using System; using Unity.Collections; using System.Collections.Generic; using System.Reflection; using Unity.Scripting.LifecycleManagement; #if UNITY_EDITOR using UnityEditor; using UnityEditor.Rendering.Universal; #endif using UnityEngine.Scripting.APIUpdating; using Lightmapping = UnityEngine.Experimental.GlobalIllumination.Lightmapping; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Profiling; using static UnityEngine.Camera; #if ENABLE_MULTI_WINDOWING && PLATFORM_SUPPORTS_PER_WINDOW_TRANSPARENCY using UnityEngine.Windowing; #endif namespace UnityEngine.Rendering.Universal { /// /// The main class for the Universal Render Pipeline (URP). /// public sealed partial class UniversalRenderPipeline : RenderPipeline { /// /// The shader tag used in the Universal Render Pipeline (URP) /// public const string k_ShaderTagName = "UniversalPipeline"; // builtin upscaler names // - {Point, Linear, FSR1} spatial upscalers; point & linear are embedded in uber post shaders, FSR1 standalone. // - {STP} temporal internal const string k_UpscalerName_Auto = "Automatic"; // this resolves to one of the following 2-3 options internal const string k_UpscalerName_Point = "Nearest-Neighbor"; internal const string k_UpscalerName_Linear = "Bilinear"; internal const string k_UpscalerName_FSR1 = "FidelityFX Super Resolution 1.0"; internal const string k_UpscalerName_STP = "Spatial-Temporal Post-Processing"; internal static readonly int k_UpscalerHash_Point = Shader.PropertyToID(k_UpscalerName_Point); internal static readonly int k_UpscalerHash_Linear = Shader.PropertyToID(k_UpscalerName_Linear); internal static readonly int k_UpscalerHash_FSR1 = Shader.PropertyToID(k_UpscalerName_FSR1); internal static readonly int k_UpscalerHash_STP = Shader.PropertyToID(k_UpscalerName_STP); #if ENABLE_UPSCALER_FRAMEWORK internal class AutoUpscaler : AbstractUpscaler { public override string name => k_UpscalerName_Auto; public override bool isTemporal => false; public override bool supportsSharpening => false; // RecordRenderGraph is an empty implementation from AbstractUpscaler } internal class BilinearUpscaler : AbstractUpscaler { public override string name => k_UpscalerName_Linear; public override bool isTemporal => false; public override bool supportsSharpening => false; } internal class PointUpscaler : AbstractUpscaler { public override string name => k_UpscalerName_Point; public override bool isTemporal => false; public override bool supportsSharpening => false; } internal class FSR1Upscaler : AbstractUpscaler { public override string name => k_UpscalerName_FSR1; public override bool isTemporal => false; public override bool supportsSharpening => true; // the FSR1 class is only for registration / unifying API for choosing an upscaler. // The pass execution logic is still carried out by the internal Fsr1UpscalePostProcessPass.cs } private static readonly HashSet k_EmbeddedUpscalerTypes = new() { typeof(AutoUpscaler), typeof(BilinearUpscaler), typeof(PointUpscaler) }; private static readonly Type[] k_UpscalerSortOrder = new Type[] { typeof(AutoUpscaler), typeof(BilinearUpscaler), typeof(PointUpscaler), typeof(FSR1Upscaler), typeof(STPIUpscaler) // Any external upscalers (DLSS, FSR2) will implicitly follow alphabetically }; #endif // Cache camera data to avoid per-frame allocations. internal static class CameraMetadataCache { public class CameraMetadataCacheEntry { public ProfilingSampler sampler; } static readonly Dictionary s_MetadataCache = new(); public static CameraMetadataCacheEntry GetCached(Camera camera) { EntityId cameraId = camera.GetEntityId(); if (!s_MetadataCache.TryGetValue(cameraId, out CameraMetadataCacheEntry result)) { // Whenever a new camera is encountered, we will need to retrieve its name. We use this allocating // frame to also prune the cache of deleted cameras (e.g. scene change or cameras destroyed from script) RemoveDeletedCameras(); string cameraName = camera.name; // Warning: camera.name allocates result = new CameraMetadataCacheEntry { sampler = new ProfilingSampler( $"{nameof(UniversalRenderPipeline)}.{nameof(RenderSingleCameraInternal)}: {cameraName}") }; s_MetadataCache.Add(cameraId, result); } return result; } static void RemoveDeletedCameras() { using (ListPool.Get(out var deletedCameras)) { foreach (var id in s_MetadataCache.Keys) { if (Resources.EntityIdToObject(id) == null) deletedCameras.Add(id); } foreach (var id in deletedCameras) { if (s_MetadataCache.TryGetValue(id, out var entry)) entry.sampler?.Dispose(); s_MetadataCache.Remove(id); } } } public static void Clear() { s_MetadataCache.Clear(); } } internal static class Profiling { public static class Pipeline { const string k_Name = nameof(UniversalRenderPipeline); public static readonly ProfilingSampler initializeCameraData = new ProfilingSampler($"{k_Name}.{nameof(CreateCameraData)}"); public static readonly ProfilingSampler initializeStackedCameraData = new ProfilingSampler($"{k_Name}.{nameof(InitializeStackedCameraData)}"); public static readonly ProfilingSampler initializeAdditionalCameraData = new ProfilingSampler($"{k_Name}.{nameof(InitializeAdditionalCameraData)}"); public static readonly ProfilingSampler initializeRenderingData = new ProfilingSampler($"{k_Name}.{nameof(CreateRenderingData)}"); public static readonly ProfilingSampler initializeShadowData = new ProfilingSampler($"{k_Name}.{nameof(CreateShadowData)}"); public static readonly ProfilingSampler initializeLightData = new ProfilingSampler($"{k_Name}.{nameof(CreateLightData)}"); public static readonly ProfilingSampler buildAdditionalLightsShadowAtlasLayout = new ProfilingSampler($"{k_Name}.{nameof(BuildAdditionalLightsShadowAtlasLayout)}"); public static readonly ProfilingSampler getPerObjectLightFlags = new ProfilingSampler($"{k_Name}.{nameof(GetPerObjectLightFlags)}"); public static readonly ProfilingSampler getMainLightIndex = new ProfilingSampler($"{k_Name}.{nameof(GetMainLightIndex)}"); public static readonly ProfilingSampler setupPerFrameShaderConstants = new ProfilingSampler($"{k_Name}.{nameof(SetupPerFrameShaderConstants)}"); public static readonly ProfilingSampler setupPerCameraShaderConstants = new ProfilingSampler($"{k_Name}.{nameof(SetupPerCameraShaderConstants)}"); public static class Renderer { const string k_Name = nameof(ScriptableRenderer); public static readonly ProfilingSampler setupCullingParameters = new ProfilingSampler($"{k_Name}.{nameof(ScriptableRenderer.SetupCullingParameters)}"); }; public static class Context { const string k_Name = nameof(ScriptableRenderContext); public static readonly ProfilingSampler submit = new ProfilingSampler($"{k_Name}.{nameof(ScriptableRenderContext.Submit)}"); }; }; } /// /// The maximum amount of bias allowed for shadows. /// public static float maxShadowBias { get => 10.0f; } /// /// The minimum value allowed for render scale. /// public static float minRenderScale { get => 0.1f; } /// /// The maximum value allowed for render scale. /// public static float maxRenderScale { get => 3.0f; } /// /// The max number of iterations allowed calculating enclosing sphere. /// public static int maxNumIterationsEnclosingSphere { get => 1000; } /// /// The max number of lights that can be shaded per object (in the for loop in the shader). /// public static int maxPerObjectLights { get => 8; } /// /// The max number of additional lights that can can affect each GameObject. /// public static int maxVisibleAdditionalLights { get { // Must match: Input.hlsl, MAX_VISIBLE_LIGHTS bool isMobileOrMobileBuildTarget = PlatformAutoDetect.isShaderAPIMobileDefined; if (isMobileOrMobileBuildTarget && (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3 && Graphics.minOpenGLESVersion <= OpenGLESVersion.OpenGLES30)) return ShaderOptions.k_MaxVisibleLightCountLowEndMobile; // GLES can be selected as platform on Windows (not a mobile platform) but uniform buffer size so we must use a low light count. // WebGPU's minimal limits are based on mobile rather than desktop, so it will need to assume mobile. return (isMobileOrMobileBuildTarget || SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore || SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.WebGPU) ? ShaderOptions.k_MaxVisibleLightCountMobile : ShaderOptions.k_MaxVisibleLightCountDesktop; } } #if UNITY_EDITOR internal static bool UseDynamicBranchFogKeyword() { const string kMemberName = "k_UseDynamicBranchFogKeyword"; Type type = typeof(ShaderOptions); MemberInfo[] memberInfo = type.GetMember(kMemberName); if (memberInfo.Length == 0) return false; int value = (int)((FieldInfo)memberInfo[0]).GetValue(null); return value == 1; } #endif // Match with values in Input.hlsl internal static int lightsPerTile => ((maxVisibleAdditionalLights + 31) / 32) * 32; internal static int maxZBinWords => 1024 * 4; internal static int maxTileWords => (maxVisibleAdditionalLights <= 32 ? 1024 : 4096) * 4; internal static int maxVisibleReflectionProbes => Math.Min(maxVisibleAdditionalLights, 64); internal const int k_DefaultRenderingLayerMask = 0x00000001; private readonly DebugDisplaySettingsUI m_DebugDisplaySettingsUI = new DebugDisplaySettingsUI(); private UniversalRenderPipelineGlobalSettings m_GlobalSettings; // Set by any camera that needs the real back buffer multisampled; one camera keeps it on for the // whole frame. Reset and consumed only in Render() so it can never be read stale. private bool m_FrameNeedsRealBackbufferMSAA; // Hysteresis on dropping back buffer MSAA to 1: counts consecutive frames not needing back buffer // MSAA so a per-frame flip-flop of the decision doesn't keep reallocating the back buffer. private int m_FramesSinceRealBackbufferMSAANeeded; // Must be > 1: when the renderer configuration changes for only a few frames (different render passes // enqueued, e.g. by the volume system), this keeps us from setting back buffer MSAA to 1 and back // again in quick succession, which reallocates the back buffer twice - costly and can stutter. // Because it counts *consecutive* not-needed frames (see above), a configuration that flips every // frame never sets MSAA to 1, so it never thrashes, for any value > 1. A higher value costs little - // the back buffer just stays multisampled for these extra frames after MSAA is genuinely no longer // needed - but is a little harder to follow when debugging or in automated tests. 6 is a conservative // default; raise it if renderer configuration changes cause back buffer thrash. internal const int realBackbufferMSAADowngradeDelayFrames = 6; internal UniversalRenderPipelineRuntimeTextures runtimeTextures { get; private set; } /// /// The default Render Pipeline Global Settings. /// public override RenderPipelineGlobalSettings defaultSettings => m_GlobalSettings; // All static variables for this class are cleaned up in Dispose(), which is always called when entering/exiting play mode. // Therefore they are marked with [NoAutoStaticsCleanup] to mute analyzer warnings about them. [NoAutoStaticsCleanup] internal static RenderGraph s_RenderGraph; [NoAutoStaticsCleanup] internal static RTHandleResourcePool s_RTHandlePool; // Store locally the value on the instance due as the Render Pipeline Asset data might change before the disposal of the asset, making some APV Resources leak. internal bool apvIsEnabled = false; // Flag to check if offscreen UI cover prepass should be executed for the current frame. [NoAutoStaticsCleanup] internal static bool requireOffscreenUICoverPrepass; // Flag to check if offscreen UI for HDR output is rendered in this frame at the first base camera. [NoAutoStaticsCleanup] internal static bool offscreenUIRenderedInCurrentFrame; // Reference to the asset associated with the pipeline. // When a pipeline asset is switched in `GraphicsSettings`, the `UniversalRenderPipelineCore.asset` member // becomes unreliable for the purpose of pipeline and renderer clean-up in the `Dispose` call from // `RenderPipelineManager.CleanupRenderPipeline`. // This field provides the correct reference for the purpose of cleaning up the renderers on this pipeline // asset. private readonly UniversalRenderPipelineAsset pipelineAsset; /// public override string ToString() => pipelineAsset?.ToString(); #if ENABLE_UPSCALER_FRAMEWORK internal static Upscaling upscaling; /// /// Gets the list of available upscaler names registered with the upscaling framework. /// /// /// A read-only list of strings containing the names of all supported upscalers. /// Returns an empty list if the upscaling framework is not initialized. /// public IReadOnlyList availableUpscalerNames { get { return upscaling?.upscalerNames ?? Array.Empty(); } } /// /// Sets the active upscaler for the pipeline by its unique name. /// /// The name of the upscaler to activate (e.g., "FidelityFX Super Resolution 1.0"). /// /// true if the upscaler exists and the assignment was successful; false if the upscaling framework is uninitialized or the specified name is invalid. /// /// /// This method updates the upscalerName on the current . /// public bool SetUpscaler(string upscalerName) { if (upscaling == null) return false; if (upscaling.IndexOf(upscalerName) == -1) return false; asset.upscalerName = upscalerName; return true; } /// /// Gets the name of the currently active upscaler. /// /// /// The name of the active upscaler, or an empty string if the upscaling framework is null. /// public string activeUpscalerName { get { if (upscaling != null) return upscaling.activeUpscaler.name; return ""; } } #endif /// /// Creates a new UniversalRenderPipeline instance. /// /// The UniversalRenderPipelineAsset asset to initialize the pipeline. /// public UniversalRenderPipeline(UniversalRenderPipelineAsset asset) { pipelineAsset = asset; m_GlobalSettings = UniversalRenderPipelineGlobalSettings.instance; runtimeTextures = GraphicsSettings.GetRenderPipelineSettings(); var shaders = GraphicsSettings.GetRenderPipelineSettings(); Blitter.Initialize(shaders.coreBlitPS, shaders.coreBlitColorAndDepthPS); SetSupportedRenderingFeatures(pipelineAsset); // Initial state of the RTHandle system. // We initialize to screen width/height to avoid multiple realloc that can lead to inflated memory usage (as releasing of memory is delayed). RTHandles.Initialize(Screen.width, Screen.height); // Init global shader keywords ShaderGlobalKeywords.InitializeShaderGlobalKeywords(); GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher; // QualitySettings.antiAliasing syncs engine MSAA state but does not reallocate the back buffer. QualitySettings.antiAliasing = asset.msaaSampleCount; // Apply the asset's MSAA to the back buffer at bind time so it doesn't lag asset changes by a // frame; ApplyRealBackbufferMSAARequest still turns it off per-frame when no camera needs it. RequestRealBackbufferMSAA(asset.msaaSampleCount); m_FramesSinceRealBackbufferMSAANeeded = 0; var defaultVolumeProfileSettings = GraphicsSettings.GetRenderPipelineSettings(); VolumeManager.instance.Initialize(defaultVolumeProfileSettings.volumeProfile, asset.volumeProfile); // Configure initial XR settings MSAASamples msaaSamples = (MSAASamples)Mathf.Clamp(Mathf.NextPowerOfTwo(QualitySettings.antiAliasing), (int)MSAASamples.None, (int)MSAASamples.MSAA8x); XRSystem.SetDisplayMSAASamples(msaaSamples); XRSystem.SetRenderScale(asset.renderScale); Lightmapping.SetDelegate(s_LightsDelegate); CameraCaptureBridge.enabled = true; RenderingUtils.ClearSystemInfoCache(); DecalProjector.defaultMaterial = asset.decalMaterial; s_RenderGraph = new RenderGraph("URPRenderGraph"); s_RTHandlePool = new RTHandleResourcePool(); DebugManager.instance.RecreateDebugUI(); #if UNITY_ENABLE_CHECKS m_DebugDisplaySettingsUI.RegisterDebug(UniversalRenderPipelineDebugDisplaySettings.Instance); #endif QualitySettings.enableLODCrossFade = asset.enableLODCrossFade; apvIsEnabled = asset != null && asset.lightProbeSystem == LightProbeSystem.ProbeVolumes; SupportedRenderingFeatures.active.overridesLightProbeSystem = apvIsEnabled; SupportedRenderingFeatures.active.skyOcclusion = apvIsEnabled; if (apvIsEnabled) { ProbeReferenceVolume.instance.Initialize(new ProbeVolumeSystemParameters { memoryBudget = asset.probeVolumeMemoryBudget, blendingMemoryBudget = asset.probeVolumeBlendingMemoryBudget, shBands = asset.probeVolumeSHBands, supportGPUStreaming = asset.supportProbeVolumeGPUStreaming, supportDiskStreaming = asset.supportProbeVolumeDiskStreaming, supportScenarios = asset.supportProbeVolumeScenarios, supportScenarioBlending = asset.supportProbeVolumeScenarioBlending, #pragma warning disable 618 sceneData = m_GlobalSettings.GetOrCreateAPVSceneData(), #pragma warning restore 618 }); } // Initializes only if VRS is supported. Vrs.InitializeResources(); #if ENABLE_UPSCALER_FRAMEWORK // URP-native (builtin) upscalers are handled here. // For the embedded upscalers {linear, point, auto}, we will provide them in a set to the upscaling system. // For the standalone pass upcalers (fsr1, stp), we'll let them register themselves. // Note: FSR1 became an exception, we will keep using the Fsr1UpscalePostProcessPass.cs instead of IUpscaler // until we address the unification of HDRP/URP texture binding for the blitter. UpscalerRegistry.Register(k_UpscalerName_Auto); UpscalerRegistry.Register(k_UpscalerName_Linear); UpscalerRegistry.Register(k_UpscalerName_Point); UpscalerRegistry.Register(k_UpscalerName_FSR1); upscaling = new Upscaling(asset.upscalerOptions, k_EmbeddedUpscalerTypes, k_UpscalerSortOrder); #endif } /// protected override void Dispose(bool disposing) { Vrs.DisposeResources(); if (apvIsEnabled) { ProbeReferenceVolume.instance.Cleanup(); } #if UNITY_ENABLE_CHECKS m_DebugDisplaySettingsUI.UnregisterDebug(); #endif Blitter.Cleanup(); base.Dispose(disposing); pipelineAsset.DestroyRenderers(); SupportedRenderingFeatures.active = new SupportedRenderingFeatures(); ShaderData.instance.Dispose(); XRSystem.Dispose(); s_RenderGraph.Cleanup(); s_RenderGraph = null; s_RTHandlePool.Cleanup(); s_RTHandlePool = null; #if UNITY_EDITOR SceneViewDrawMode.ResetDrawMode(); #endif Lightmapping.ResetDelegate(); CameraCaptureBridge.enabled = false; ConstantBuffer.ReleaseAll(); VolumeManager.instance.Deinitialize(); DisposeAdditionalCameraData(); AdditionalLightsShadowAtlasLayout.ClearStaticCaches(); CameraMetadataCache.Clear(); requireOffscreenUICoverPrepass = false; offscreenUIRenderedInCurrentFrame = false; #if ENABLE_UPSCALER_FRAMEWORK // Dispose only clears the contexts, leaving the registered-upscaler list intact. // The command buffer signals upscaler plugins on the render thread to release their native resources; // it records no GPU work. Release can be asynchronous, so this can't be a plain managed Dispose on the // main thread. ExecuteCommandBuffer dispatches the signal immediately, as there's no render context to // submit to at pipeline teardown. if (upscaling != null) { var upscalerCmd = CommandBufferPool.Get("Upscaler Dispose"); upscaling.Dispose(upscalerCmd); Graphics.ExecuteCommandBuffer(upscalerCmd); CommandBufferPool.Release(upscalerCmd); } #endif } // If the URP gets destroyed, we must clean up all the added URP specific camera data and // non-GC resources to avoid leaking them. private void DisposeAdditionalCameraData() { foreach (var c in Camera.allCameras) { if (c.TryGetComponent(out var additionalCameraData)) { additionalCameraData.historyManager.Dispose(); }; } } readonly struct CameraRenderingScope : IDisposable { static readonly ProfilingSampler beginCameraRenderingSampler = new ProfilingSampler($"{nameof(RenderPipeline)}.{nameof(BeginCameraRendering)}"); static readonly ProfilingSampler endCameraRenderingSampler = new ProfilingSampler($"{nameof(RenderPipeline)}.{nameof(EndCameraRendering)}"); private readonly ScriptableRenderContext m_Context; private readonly Camera m_Camera; public CameraRenderingScope(ScriptableRenderContext context, Camera camera) { using (new ProfilingScope(beginCameraRenderingSampler, camera)) { m_Context = context; m_Camera = camera; BeginCameraRendering(context, camera); } } public void Dispose() { using (new ProfilingScope(endCameraRenderingSampler, m_Camera)) { EndCameraRendering(m_Context, m_Camera); } } } readonly struct ContextRenderingScope : IDisposable { static readonly ProfilingSampler beginContextRenderingSampler = new ProfilingSampler($"{nameof(RenderPipeline)}.{nameof(BeginContextRendering)}"); static readonly ProfilingSampler endContextRenderingSampler = new ProfilingSampler($"{nameof(RenderPipeline)}.{nameof(EndContextRendering)}"); private readonly ScriptableRenderContext m_Context; private readonly List m_Cameras; public ContextRenderingScope(ScriptableRenderContext context, List cameras) { m_Context = context; m_Cameras = cameras; using (new ProfilingScope(beginContextRenderingSampler)) { BeginContextRendering(m_Context, m_Cameras); } } public void Dispose() { using (new ProfilingScope(endContextRenderingSampler)) { EndContextRendering(m_Context, m_Cameras); } } } /// protected override void Render(ScriptableRenderContext renderContext, List cameras) { // RenderPipelineManager calls this once per frame: reset the back buffer MSAA request, let each // camera rendered here report its need, then apply. Cameras rendered outside this path (render // requests) are not counted — safe only because they target a destination texture, never the back // buffer. If that changes, this decision becomes incomplete. m_FrameNeedsRealBackbufferMSAA = false; RenderCameras(renderContext, cameras); ApplyRealBackbufferMSAARequest(); } void RenderCameras(ScriptableRenderContext renderContext, List cameras) { SetHDRState(cameras); int cameraCount = cameras.Count; // For XR, HDR and no camera cases, UI Overlay ownership must be enforced AdjustUIOverlayOwnership(cameraCount); // When HDR output is enabled, SRP renders the overlay UI per camera viewport, so any screen area not covered by viewports won’t display the UI. // The offscreen UI cover prepass ensures the overlay UI covers the entire display by blitting UI to the screen first, even when the combined camera viewports do not fill the screen. requireOffscreenUICoverPrepass = HDROutputForMainDisplayIsActive() && asset.supportsHDR && SupportedRenderingFeatures.active.rendersUIOverlay && !CoreUtils.IsScreenFullyCoveredByCameras(cameras); GPUResidentDrawer.ReinitializeIfNeeded(); // TODO: Would be better to add Profiling name hooks into RenderPipelineManager. // C#8 feature, only in >= 2020.2 using var profScope = new ProfilingScope(URPProfilingSamplers.UniversalRenderTotal); using (new ContextRenderingScope(renderContext, cameras)) { GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear); GraphicsSettings.lightsUseColorTemperature = true; SetupPerFrameShaderConstants(); XRSystem.SetDisplayMSAASamples((MSAASamples)asset.msaaSampleCount); #if UNITY_ENABLE_CHECKS if (DebugManager.instance.isAnyDebugUIActive) UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateDisplayStats(); // This is for texture streaming UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateMaterials(); #endif #if ENABLE_UPSCALER_FRAMEWORK upscaling.SetActiveUpscaler(asset.upscalerName); #endif // URP uses the camera's allowDynamicResolution flag to decide if useDynamicScale should be enabled for camera render targets. // However, the RTHandle system has an additional setting that controls if useDynamicScale will be set for render targets allocated via RTHandles. // In order to avoid issues at runtime, we must make the RTHandle system setting consistent with URP's logic. URP already synchronizes the setting // during initialization, but unfortunately it's possible for external code to overwrite the setting due to RTHandle state being global. // The best we can do to avoid errors in this situation is to ensure the state is set to the correct value every time we perform rendering. RTHandles.SetHardwareDynamicResolutionState(true); SortCameras(cameras); int lastBaseCameraIndex = GetLastBaseCameraIndex(cameras); offscreenUIRenderedInCurrentFrame = false; for (int i = 0; i < cameraCount; ++i) { // camera can be a base or an overlay camera var camera = cameras[i]; bool isLastBaseCamera = i == lastBaseCameraIndex; if (IsGameCamera(camera)) { // Only render the stack if camera is a base camera RenderCameraStack(renderContext, camera, isLastBaseCamera); } else { using (new CameraRenderingScope(renderContext, camera)) { #if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER //It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect. //N.B.: We aren't expecting an XR camera at this stage VFX.VFXManager.PrepareCamera(camera); #endif camera.TryGetComponent(out var additionalCameraData); UpdateVolumeFramework(camera, additionalCameraData); // Only render if camera is a base camera RenderSingleCameraInternal(renderContext, camera, isLastBaseCamera); } } } s_RenderGraph.EndFrame(); s_RTHandlePool.PurgeUnusedResources(Time.frameCount); #if ENABLE_UPSCALER_FRAMEWORK // Clean up expired upscaler contexts (unused for > 400 frames) if (upscaling != null) { var cmd = CommandBufferPool.Get("Upscaler Context Cleanup"); upscaling.CleanupExpiredContexts(cmd); renderContext.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } #endif } #if ENABLE_SHADER_DEBUG_PRINT ShaderDebugPrintManager.instance.EndFrame(); #endif } // Renderers call this (via ScriptableRenderer.ReportRealBackbufferMSAA) to report that a camera needs // the real back buffer multisampled. internal void RequireRealBackbufferMSAA() { m_FrameNeedsRealBackbufferMSAA = true; } // Only sets the request for the next frame. The current frame already imported the live back buffer // value (Screen.currentBackbufferMSAASamples) into RenderGraph, so rendering stays correct no matter // when the surface is actually reallocated (deferred to the next frame on platforms like Metal). // Upgrades apply immediately; downgrades wait out the hysteresis, since extra MSAA frames cost only // bandwidth, never correctness. private void ApplyRealBackbufferMSAARequest() { int desired; if (m_FrameNeedsRealBackbufferMSAA) { m_FramesSinceRealBackbufferMSAANeeded = 0; desired = asset.msaaSampleCount; // upgrade immediately } else if (++m_FramesSinceRealBackbufferMSAANeeded >= realBackbufferMSAADowngradeDelayFrames) desired = 1; // downgrade only after the delay else desired = Screen.msaaSamples; // hold current during the delay RequestRealBackbufferMSAA(desired); } // Sets the real back buffer MSAA count, clamped to 1 where MSAA is resolved explicitly (the // optimization is inactive there). Reallocates only when the count actually changes vs the engine's // currently requested count (Screen.msaaSamples), so this also respects external changes to it. private void RequestRealBackbufferMSAA(int samples) { if (UniversalRenderer.PlatformRequiresExplicitMsaaResolve()) samples = 1; if (samples != Screen.msaaSamples) Screen.SetMSAASamples(samples); } /// /// Check whether RenderRequest is supported /// /// /// /// /// protected override bool IsRenderRequestSupported(Camera camera, RequestData data) { if (data is StandardRequest) return true; else if (data is SingleCameraRequest) return true; return false; } /// /// Process a render request /// /// /// /// /// protected override void ProcessRenderRequests(ScriptableRenderContext context, Camera camera, RequestData renderRequest) { StandardRequest standardRequest = renderRequest as StandardRequest; SingleCameraRequest singleRequest = renderRequest as SingleCameraRequest; if(standardRequest != null || singleRequest != null) { RenderTexture destination = standardRequest != null ? standardRequest.destination : singleRequest.destination; //don't go further if no destination texture if(destination == null) { Debug.LogError("RenderRequest has no destination texture, set one before sending request"); return; } int mipLevel = standardRequest != null ? standardRequest.mipLevel : singleRequest.mipLevel; int slice = standardRequest != null ? standardRequest.slice : singleRequest.slice; int face = standardRequest != null ? (int)standardRequest.face : (int)singleRequest.face; bool isPreview = standardRequest != null ? standardRequest.isPreview : false; //store data that will be changed var originalCameraType = camera.cameraType; var originalTarget = camera.targetTexture; //set data RenderTexture temporaryRT = null; RenderTextureDescriptor RTDesc = destination.descriptor; //need to set use default constructor of RenderTextureDescriptor which doesn't enable allowVerticalFlip which matters for cubemaps. if (destination.dimension == TextureDimension.Cube) RTDesc = new RenderTextureDescriptor(); RTDesc.colorFormat = destination.format; RTDesc.volumeDepth = 1; RTDesc.msaaSamples = destination.descriptor.msaaSamples; RTDesc.dimension = TextureDimension.Tex2D; RTDesc.width = destination.width / (int)Math.Pow(2, mipLevel); RTDesc.height = destination.height / (int)Math.Pow(2, mipLevel); RTDesc.width = Mathf.Max(1, RTDesc.width); RTDesc.height = Mathf.Max(1, RTDesc.height); //if mip is 0 and target is Texture2D we can immediately render to the requested destination if(destination.dimension != TextureDimension.Tex2D || mipLevel != 0) { temporaryRT = RenderTexture.GetTemporary(RTDesc); } if (isPreview) camera.cameraType = CameraType.Preview; camera.targetTexture = temporaryRT ? temporaryRT : destination; // Do not call Render() here — it must run once per frame (see its back buffer MSAA bookkeeping). if (standardRequest != null) { RenderCameras(context, new List{ camera }); } else { using (ListPool.Get(out var tmp)) { tmp.Add(camera); using (new ContextRenderingScope(context, tmp)) using (new CameraRenderingScope(context, camera)) { camera.gameObject.TryGetComponent(out var additionalCameraData); RenderSingleCameraInternal(context, camera, ref additionalCameraData); } } } if(temporaryRT) { bool isCopySupported = false; switch(destination.dimension) { case TextureDimension.Tex2D: if((SystemInfo.copyTextureSupport & CopyTextureSupport.Basic) != 0) { isCopySupported = true; Graphics.CopyTexture(temporaryRT, 0, 0, destination, 0, mipLevel); } break; case TextureDimension.Tex2DArray: if((SystemInfo.copyTextureSupport & CopyTextureSupport.DifferentTypes) != 0) { isCopySupported = true; Graphics.CopyTexture(temporaryRT, 0, 0, destination, slice, mipLevel); } break; case TextureDimension.Tex3D: if((SystemInfo.copyTextureSupport & CopyTextureSupport.DifferentTypes) != 0) { isCopySupported = true; Graphics.CopyTexture(temporaryRT, 0, 0, destination, slice, mipLevel); } break; case TextureDimension.Cube: if((SystemInfo.copyTextureSupport & CopyTextureSupport.DifferentTypes) != 0) { isCopySupported = true; Graphics.CopyTexture(temporaryRT, 0, 0, destination, face, mipLevel); } break; case TextureDimension.CubeArray: if((SystemInfo.copyTextureSupport & CopyTextureSupport.DifferentTypes) != 0) { isCopySupported = true; Graphics.CopyTexture(temporaryRT, 0, 0, destination, face + slice * 6, mipLevel); } break; default: break; } if(!isCopySupported) Debug.LogError("RenderRequest cannot have destination texture of this format: " + Enum.GetName(typeof(TextureDimension), destination.dimension)); } //restore data camera.cameraType = originalCameraType; camera.targetTexture = originalTarget; Graphics.SetRenderTarget(originalTarget); RenderTexture.ReleaseTemporary(temporaryRT); } else { Debug.LogWarning("RenderRequest type: " + typeof(RequestData).FullName + " is either invalid or unsupported by the current pipeline"); } } /// /// Standalone camera rendering. Use this to render procedural cameras. /// This method doesn't call BeginCameraRendering and EndCameraRendering callbacks. /// /// Render context used to record commands during execution. /// Camera to render. /// [Obsolete("RenderSingleCamera is obsolete, please use RenderPipeline.SubmitRenderRequest with UniversalRenderer.SingleCameraRequest as RequestData type. #from(2023.1)")] public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera) { RenderSingleCameraInternal(context, camera); } internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, bool isLastBaseCamera = true) { UniversalAdditionalCameraData additionalCameraData = null; #if URP_SCREEN_SPACE_REFLECTION || MODERN_SSAO camera.gameObject.TryGetComponent(out additionalCameraData); // Some scene view effects require camera history, which is owned by UniversalAdditionalCameraData. // Add the component on demand so those effects can access a history manager for the scene view camera. if (camera.cameraType == CameraType.SceneView && additionalCameraData == null) additionalCameraData = camera.gameObject.AddComponent(); #else if (IsGameCamera(camera)) camera.gameObject.TryGetComponent(out additionalCameraData); #endif RenderSingleCameraInternal(context, camera, ref additionalCameraData, isLastBaseCamera); } internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, ref UniversalAdditionalCameraData additionalCameraData, bool isLastBaseCamera = true) { if (additionalCameraData != null && additionalCameraData.renderType != CameraRenderType.Base) { Debug.LogWarning("Only Base cameras can be rendered with standalone RenderSingleCamera. Camera will be skipped."); return; } if (camera.targetTexture.width == 0 || camera.targetTexture.height == 0 || camera.pixelWidth == 0 || camera.pixelHeight == 0) { Debug.LogWarning($"Camera '{camera.name}' has an invalid render target size (width: {camera.targetTexture.width}, height: {camera.targetTexture.height}) or pixel dimensions (width: {camera.pixelWidth}, height: {camera.pixelHeight}). Camera will be skipped."); return; } var frameData = GetRenderer(camera, additionalCameraData).frameData; var cameraData = CreateCameraData(frameData, camera, additionalCameraData); InitializeAdditionalCameraData(camera, additionalCameraData, true, isLastBaseCamera, cameraData); #if ENABLE_ADAPTIVE_PERFORMANCE if (asset?.useAdaptivePerformance == true) ApplyAdaptivePerformance(cameraData); #endif RenderSingleCamera(context, cameraData); } #if ENABLE_VR && ENABLE_XR_MODULE static private LODParameters cachedLODParameters; #endif static bool TryGetCullingParameters(UniversalCameraData cameraData, out ScriptableCullingParameters cullingParams) { #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled) { cullingParams = cameraData.xr.cullingParams; // Sync the FOV on the camera to match the projection from the XR device if (!cameraData.camera.usePhysicalProperties && !XRGraphicsAutomatedTests.enabled) cameraData.camera.fieldOfView = Mathf.Rad2Deg * Mathf.Atan(1.0f / cullingParams.stereoProjectionMatrix.m11) * 2.0f; if (cameraData.xr.isFirstCameraPass) { cachedLODParameters = cullingParams.lodParameters; cachedLODParameters.fieldOfView = cameraData.camera.fieldOfView; // Update it in case it was synced above cullingParams.lodParameters = cachedLODParameters; } else cullingParams.lodParameters = cachedLODParameters; // For Quad Views, ensures that the inset pass will use the same mesh LODs as the outset pass return true; } #endif return cameraData.camera.TryGetCullingParameters(false, out cullingParams); } /// /// Renders a single camera. This method will do culling, setup and execution of the renderer. /// /// Render context used to record commands during execution. /// Camera rendering data. This might contain data inherited from a base camera. static void RenderSingleCamera(ScriptableRenderContext context, UniversalCameraData cameraData) { Camera camera = cameraData.camera; ScriptableRenderer renderer = cameraData.renderer; if (renderer == null) { Debug.LogWarning(string.Format("Trying to render {0} with an invalid renderer. Camera rendering will be skipped.", camera.name)); return; } // Note: We are disposing frameData once this variable goes out of scope. using ContextContainer frameData = renderer.frameData; if (!TryGetCullingParameters(cameraData, out var cullingParameters)) return; ScriptableRenderer.current = renderer; bool isSceneViewCamera = cameraData.isSceneViewCamera; // NOTE: Do NOT mix ProfilingScope with named CommandBuffers i.e. CommandBufferPool.Get("name"). // Currently there's an issue which results in mismatched markers. // The named CommandBuffer will close its "profiling scope" on execution. // That will orphan ProfilingScope markers as the named CommandBuffer markers are their parents. // Resulting in following pattern: // exec(cmd.start, scope.start, cmd.end) and exec(cmd.start, scope.end, cmd.end) CommandBuffer cmd = CommandBufferPool.Get(); // TODO: move skybox code from C++ to URP in order to remove the call to context.Submit() inside DrawSkyboxPass // Until then, we can't use nested profiling scopes with XR multipass CommandBuffer cmdScope = cameraData.xr.enabled ? null : cmd; var cameraMetadata = CameraMetadataCache.GetCached(camera); using (new ProfilingScope(cmdScope, cameraMetadata.sampler, camera)) // Enqueues a "BeginSample" command into the CommandBuffer cmd { using (new ProfilingScope(Profiling.Pipeline.Renderer.setupCullingParameters, camera)) { var legacyCameraData = new CameraData(frameData); renderer.OnPreCullRenderPasses(in legacyCameraData); renderer.SetupCullingParameters(ref cullingParameters, ref legacyCameraData); } context.ExecuteCommandBuffer(cmd); // Send all the commands enqueued so far in the CommandBuffer cmd, to the ScriptableRenderContext context cmd.Clear(); SetupPerCameraShaderConstants(cmd); ProbeVolumesOptions apvOptions = null; if (camera.TryGetComponent(out var additionalCameraData)) apvOptions = additionalCameraData.volumeStack?.GetComponent(); bool supportProbeVolume = asset != null && asset.lightProbeSystem == LightProbeSystem.ProbeVolumes; ProbeReferenceVolume.instance.SetEnableStateFromSRP(supportProbeVolume); ProbeReferenceVolume.instance.SetVertexSamplingEnabled(asset.shEvalMode == ShEvalMode.PerVertex || asset.shEvalMode == ShEvalMode.Mixed); // We need to verify and flush any pending asset loading for probe volume. if (supportProbeVolume && ProbeReferenceVolume.instance.isInitialized) { ProbeReferenceVolume.instance.PerformPendingOperations(); if (camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview) { // TODO: Move this to one call for all cameras ProbeReferenceVolume.instance.UpdateCellStreaming(cmd, camera, apvOptions); } } // Emit scene/game view UI. The main game camera UI is always rendered, so this needs to be handled only for different camera types if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview) ScriptableRenderContext.EmitGeometryForCamera(camera); #if UNITY_EDITOR else if (isSceneViewCamera) ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); #endif // do AdaptiveProbeVolume stuff if (supportProbeVolume) ProbeReferenceVolume.instance.BindAPVRuntimeResources(cmd, true); // Must be called before culling because it emits intermediate renderers via Graphics.DrawInstanced. ProbeReferenceVolume.instance.RenderDebug(camera, apvOptions, Texture2D.whiteTexture); // Update camera motion tracking (prev matrices) from cameraData. // Called and updated only once, as the same camera can be rendered multiple times. // NOTE: Tracks only the current (this) camera, not shadow views or any other offscreen views. // NOTE: Shared between both Execute and Render (RG) paths. if (additionalCameraData != null) additionalCameraData.motionVectorsPersistentData.Update(cameraData); // TODO: Move into the renderer. Problem: It modifies the AdditionalCameraData which is copied into RenderingData which causes value divergence for value types. // Update TAA persistent data based on cameraData. Most importantly resize the history render targets. // NOTE: Persistent data is kept over multiple frames. Its life-time differs from typical resources. // NOTE: Shared between both Execute and Render (RG) paths. if (cameraData.taaHistory != null) UpdateTemporalAATargets(cameraData); RTHandles.SetReferenceSize(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height); // Do NOT use cameraData after 'InitializeRenderingData'. CameraData state may diverge otherwise. // RenderingData takes a copy of the CameraData. // UniversalRenderingData needs to be created here to avoid copying cullResults. var data = frameData.Create(); #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled) { var xrPassUniversal = cameraData.xr as XRPassUniversal; data.reuseCullingResult = cameraData.xr.isQuadViewInnerPass; if (!data.reuseCullingResult) xrPassUniversal.cullingResults = context.Cull(ref cullingParameters); data.cullResults = xrPassUniversal.cullingResults; } else #endif data.cullResults = context.Cull(ref cullingParameters); GPUResidentDrawer.PostCullBeginCameraRendering(new RenderRequestBatcherContext { commandBuffer = cmd }); RenderingMode? renderingMode = (cameraData.renderer as UniversalRenderer)?.renderingModeActual; // Initialize all the data types required for rendering. UniversalLightData lightData; UniversalShadowData shadowData; CullContextData cullData; using (new ProfilingScope(Profiling.Pipeline.initializeRenderingData, camera)) { CreateUniversalResourceData(frameData); lightData = CreateLightData(frameData, asset, data.cullResults.visibleLights, renderingMode); shadowData = CreateShadowData(frameData, asset, renderingMode); CreatePostProcessingData(frameData, asset); CreateRenderingData(frameData, asset, cmd, renderingMode, cameraData.renderer); cullData = CreateCullContextData(frameData, context); } RenderingData legacyRenderingData = new RenderingData(frameData); CheckAndApplyDebugSettings(ref legacyRenderingData); #if ENABLE_ADAPTIVE_PERFORMANCE if (asset?.useAdaptivePerformance == true) ApplyAdaptivePerformance(frameData); #endif #if ENABLE_VR && ENABLE_XR_MODULE shadowData.supportShadowMapCaching = cameraData.xr.enabled && cameraData.xr.xrLayoutType == XRLayoutType.TwoPassQuadViews; shadowData.useCachedShadowMap = shadowData.supportShadowMapCaching && cameraData.xr.isQuadViewInnerPass; #endif CreateShadowAtlasAndCullShadowCasters(lightData, shadowData, cameraData, ref data.cullResults, ref context); renderer.AddRenderPasses(ref legacyRenderingData); RecordAndExecuteRenderGraph(s_RenderGraph, context, renderer, cmd, cameraData.camera); renderer.FinishRenderGraphRendering(cmd); } // When ProfilingSample goes out of scope, an "EndSample" command is enqueued into CommandBuffer cmd context.ExecuteCommandBuffer(cmd); // Sends to ScriptableRenderContext all the commands enqueued since cmd.Clear, i.e the "EndSample" command CommandBufferPool.Release(cmd); using (new ProfilingScope(Profiling.Pipeline.Context.submit, cameraData.camera)) { context.Submit(); // Actually execute the commands that we previously sent to the ScriptableRenderContext context } ScriptableRenderer.current = null; } private static void CreateShadowAtlasAndCullShadowCasters(UniversalLightData lightData, UniversalShadowData shadowData, UniversalCameraData cameraData, ref CullingResults cullResults, ref ScriptableRenderContext context) { if (!shadowData.supportsMainLightShadows && !shadowData.supportsAdditionalLightShadows) return; if (shadowData.supportsMainLightShadows) InitializeMainLightShadowResolution(shadowData); if (shadowData.supportsAdditionalLightShadows) shadowData.shadowAtlasLayout = BuildAdditionalLightsShadowAtlasLayout(lightData, shadowData, cameraData); #if ENABLE_VR && ENABLE_XR_MODULE if (shadowData.useCachedShadowMap) return; #endif shadowData.visibleLightsShadowCullingInfos = ShadowCulling.CullShadowCasters(ref context, shadowData, ref shadowData.shadowAtlasLayout, ref cullResults); } /// /// Renders a camera stack if the selected camera is a base camera. /// This method calls RenderSingleCamera for each valid camera in the stack. /// The last camera resolves the final target to screen. /// /// Render context used to record commands during execution. /// Camera to render. /// True if this is the last base camera. static void RenderCameraStack(ScriptableRenderContext context, Camera baseCamera, bool isLastBaseCamera) { using var profScope = new ProfilingScope(URPProfilingSamplers.RenderCameraStack, baseCamera); baseCamera.TryGetComponent(out var baseCameraAdditionalData); // Overlay cameras will be rendered stacked while rendering base cameras if (baseCameraAdditionalData != null && baseCameraAdditionalData.renderType == CameraRenderType.Overlay) return; // Renderer contains a stack if it has additional data and the renderer supports stacking // The renderer is checked if it supports Base camera. Since Base is the only relevant type at this moment. var renderer = GetRenderer(baseCamera, baseCameraAdditionalData); bool supportsCameraStacking = renderer != null && renderer.SupportsCameraStackingType(CameraRenderType.Base); List stackedOverlayCameras = (supportsCameraStacking) ? baseCameraAdditionalData?.cameraStack : null; // We use this bool to check if post processing is enabled for any cameras of the stack bool stackAnyPostProcessingEnabled = baseCameraAdditionalData != null && baseCameraAdditionalData.renderPostProcessing; bool mainHdrDisplayOutputActive = HDROutputForMainDisplayIsActive(); // We need to know the last active camera in the stack to be able to resolve // rendering to screen when rendering it. The last camera in the stack is not // necessarily the last active one as it users might disable it. int lastActiveOverlayCameraIndex = -1; if (stackedOverlayCameras != null) { var baseCameraRendererType = renderer.GetType(); bool shouldUpdateCameraStack = false; for (int i = 0; i < stackedOverlayCameras.Count; ++i) { Camera overlayCamera = stackedOverlayCameras[i]; if (overlayCamera == null) { shouldUpdateCameraStack = true; continue; } if (overlayCamera.isActiveAndEnabled) { overlayCamera.TryGetComponent(out var data); var overlayRenderer = GetRenderer(overlayCamera, data); // Checking if the base and the overlay camera is of the same renderer type. var overlayRendererType = overlayRenderer.GetType(); if (overlayRendererType != baseCameraRendererType) { Debug.LogWarning("Only cameras with compatible renderer types can be stacked. " + $"The camera: {overlayCamera.name} are using the renderer {overlayRendererType.Name}, " + $"but the base camera: {baseCamera.name} are using {baseCameraRendererType.Name}. Will skip rendering"); continue; } // Checking if they are the same renderer type but just not supporting Overlay if ((overlayRenderer.SupportedCameraStackingTypes() & 1 << (int)CameraRenderType.Overlay) == 0) { Debug.LogWarning($"The camera: {overlayCamera.name} is using a renderer of type {renderer.GetType().Name} which does not support Overlay cameras in it's current state."); continue; } if (data == null || data.renderType != CameraRenderType.Overlay) { Debug.LogWarning($"Stack can only contain Overlay cameras. The camera: {overlayCamera.name} " + $"has a type {data.renderType} that is not supported. Will skip rendering."); continue; } stackAnyPostProcessingEnabled |= data.renderPostProcessing; lastActiveOverlayCameraIndex = i; } } if (shouldUpdateCameraStack) { baseCameraAdditionalData.UpdateCameraStack(); } } bool isStackedRendering = lastActiveOverlayCameraIndex != -1; // The camera data is set based on the supported features. renderer.UpdateSupportedRenderingFeatures(); // Prepare XR rendering var xrActive = false; var xrRendering = baseCameraAdditionalData?.allowXRRendering ?? true; var xrLayout = XRSystem.NewLayout(); xrLayout.AddCamera(baseCamera, xrRendering); XRPassUniversal prevXRPassUniversal = null; // With XR multi-pass enabled, each camera can be rendered multiple times with different parameters foreach ((Camera _, XRPass xrPass) in xrLayout.GetActivePasses()) { var xrPassUniversal = xrPass as XRPassUniversal; // For inner QuadView pass: copy cached culling results from the outer pass so RenderSingleCamera // can reuse them without issuing a second context.Cull() call. if (prevXRPassUniversal != null && xrPassUniversal != null && xrPass.isQuadViewInnerPass) xrPassUniversal.cullingResults = prevXRPassUniversal.cullingResults; if (xrPass.enabled) { xrActive = true; UpdateCameraStereoMatrices(baseCamera, xrPass); // Apply XR display's viewport scale to URP's dynamic resolution solution float scaleToApply = XRSystem.GetRenderViewportScale(); ScalableBufferManager.ResizeBuffers(scaleToApply, scaleToApply); } bool finalOutputHDR = false; #if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER VFX.VFXCameraXRSettings cameraXRSettings; #endif // Base Camera Rendering using (new CameraRenderingScope(context, baseCamera)) { // Update volumeframework before initializing additional camera data UpdateVolumeFramework(baseCamera, baseCameraAdditionalData); ContextContainer frameData = renderer.frameData; UniversalCameraData baseCameraData = CreateCameraData(frameData, baseCamera, baseCameraAdditionalData); #if ENABLE_VR && ENABLE_XR_MODULE if (xrPass.enabled) { baseCameraData.xr = xrPass; // Helper function for updating cameraData with xrPass Data // Need to update XRSystem using baseCameraData to handle the case where camera position is modified in BeginCameraRendering UpdateCameraData(baseCameraData, xrPass); // Handle the case where camera position is modified in BeginCameraRendering xrLayout.ReconfigurePass(xrPass, baseCamera); XRSystemUniversal.BeginLateLatching(baseCamera, xrPassUniversal); } #endif // InitializeAdditionalCameraData needs to be initialized after the cameraTargetDescriptor is set because it needs to know the // msaa level of cameraTargetDescriptor and XR modifications. InitializeAdditionalCameraData(baseCamera, baseCameraAdditionalData, !isStackedRendering, isLastBaseCamera, baseCameraData); #if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER //It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect. cameraXRSettings.viewTotal = baseCameraData.xr.enabled ? 2u : 1u; cameraXRSettings.viewCount = baseCameraData.xr.enabled ? (uint)baseCameraData.xr.viewCount : 1u; cameraXRSettings.viewOffset = (uint)baseCameraData.xr.multipassId; VFX.VFXManager.PrepareCamera(baseCamera, cameraXRSettings); #endif #if ENABLE_ADAPTIVE_PERFORMANCE if (asset?.useAdaptivePerformance == true) ApplyAdaptivePerformance(baseCameraData); #endif // Check whether the camera stack final output is HDR // This is equivalent of UniversalCameraData.isHDROutputActive but without necessiting the base camera to be the last camera in the stack. bool hdrDisplayOutputActive = mainHdrDisplayOutputActive; #if ENABLE_VR && ENABLE_XR_MODULE // If we are rendering to xr then we need to look at the XR Display rather than the main non-xr display. if (xrPass.enabled) hdrDisplayOutputActive = xrPass.isHDRDisplayOutputActive; #endif finalOutputHDR = hdrDisplayOutputActive // Check whether any HDR display is active and the render pipeline asset allows HDR rendering && baseCamera.targetTexture == null && (baseCamera.cameraType == CameraType.Game || baseCamera.cameraType == CameraType.VR) // Check whether the stack outputs to a screen && baseCameraData.isHdrEnabled; // Check whether the base camera has HDR enabled (this includes a check if the renderer supports it) // Update stack-related parameters baseCameraData.stackAnyPostProcessingEnabled = stackAnyPostProcessingEnabled; baseCameraData.stackLastCameraOutputToHDR = finalOutputHDR; // Render the HDR offscreen overlay UI only in the first base camera if it renders overlay UI. UpdateOffscreenUIRendering(baseCameraData, finalOutputHDR); RenderSingleCamera(context, baseCameraData); } // Late latching is not supported after this point if (xrPass.enabled) XRSystemUniversal.EndLateLatching(baseCamera, xrPassUniversal); // Overlay Cameras Rendering if (isStackedRendering) { for (int i = 0; i < stackedOverlayCameras.Count; ++i) { var overlayCamera = stackedOverlayCameras[i]; if (!overlayCamera.isActiveAndEnabled) continue; overlayCamera.TryGetComponent(out var overlayAdditionalCameraData); // Camera is overlay and enabled if (overlayAdditionalCameraData != null) { ContextContainer overlayFrameData = GetRenderer(overlayCamera, overlayAdditionalCameraData).frameData; UniversalCameraData overlayCameraData = CreateCameraData(overlayFrameData, baseCamera, baseCameraAdditionalData); #if ENABLE_VR && ENABLE_XR_MODULE if (xrPass.enabled) { overlayCameraData.xr = xrPass; UpdateCameraData(overlayCameraData, xrPass); } #endif InitializeAdditionalCameraData(overlayCamera, overlayAdditionalCameraData, false, isLastBaseCamera, overlayCameraData); overlayCameraData.camera = overlayCamera; overlayCameraData.baseCamera = baseCamera; UpdateCameraStereoMatrices(overlayAdditionalCameraData.camera, xrPass); using (new CameraRenderingScope(context, overlayCamera)) { #if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER //It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect. VFX.VFXManager.PrepareCamera(overlayCamera, cameraXRSettings); #endif UpdateVolumeFramework(overlayCamera, overlayAdditionalCameraData); bool isLastOverlayCamera = i == lastActiveOverlayCameraIndex; InitializeAdditionalCameraData(overlayCamera, overlayAdditionalCameraData, isLastOverlayCamera, isLastBaseCamera, overlayCameraData); overlayCameraData.stackAnyPostProcessingEnabled = stackAnyPostProcessingEnabled; overlayCameraData.stackLastCameraOutputToHDR = finalOutputHDR; // Render the HDR offscreen overlay UI from the stack's last camera if earlier base camera did not render overlay UI. if (isLastOverlayCamera) UpdateOffscreenUIRendering(overlayCameraData, finalOutputHDR); xrLayout.ReconfigurePass(overlayCameraData.xr, overlayCamera); RenderSingleCamera(context, overlayCameraData); } } } } prevXRPassUniversal = xrPassUniversal; } if (xrActive) { CommandBuffer cmd = CommandBufferPool.Get(); XRSystem.RenderMirrorView(cmd, baseCamera); context.ExecuteCommandBuffer(cmd); context.Submit(); CommandBufferPool.Release(cmd); } XRSystem.EndLayout(); } // Used for updating URP cameraData data struct with XRPass data. static void UpdateCameraData(UniversalCameraData baseCameraData, in XRPass xr) { // Update cameraData viewport for XR Rect cameraRect = baseCameraData.camera.rect; Rect xrViewport = xr.GetViewport(); baseCameraData.pixelRect = new Rect(cameraRect.x * xrViewport.width + xrViewport.x, cameraRect.y * xrViewport.height + xrViewport.y, cameraRect.width * xrViewport.width, cameraRect.height * xrViewport.height); Rect camPixelRect = baseCameraData.pixelRect; baseCameraData.pixelWidth = (int)System.Math.Round(camPixelRect.width + camPixelRect.x) - (int)System.Math.Round(camPixelRect.x); baseCameraData.pixelHeight = (int)System.Math.Round(camPixelRect.height + camPixelRect.y) - (int)System.Math.Round(camPixelRect.y); baseCameraData.aspectRatio = (float)baseCameraData.pixelWidth / (float)baseCameraData.pixelHeight; // XR backbuffer info — sourced directly from the XR render target descriptor. baseCameraData.backbufferColor = new RenderTargetInfo { width = xr.renderTargetDesc.width, height = xr.renderTargetDesc.height, volumeDepth = xr.renderTargetDesc.volumeDepth, msaaSamples = xr.renderTargetDesc.msaaSamples, format = xr.renderTargetDesc.graphicsFormat, bindMS = xr.renderTargetDesc.msaaSamples > 1 && !UniversalRenderer.PlatformRequiresExplicitMsaaResolve(), }; baseCameraData.backbufferDepth = baseCameraData.backbufferColor; baseCameraData.backbufferDepth.format = xr.renderTargetDesc.depthStencilFormat; // Update cameraData cameraTargetDescriptor for XR. This descriptor is mainly used for configuring intermediate screen space textures var originalTargetDesc = baseCameraData.cameraTargetDescriptor; baseCameraData.cameraTargetDescriptor = xr.renderTargetDesc; if (baseCameraData.isHdrEnabled) { baseCameraData.cameraTargetDescriptor.graphicsFormat = originalTargetDesc.graphicsFormat; } baseCameraData.cameraTargetDescriptor.msaaSamples = originalTargetDesc.msaaSamples; if (baseCameraData.isDefaultViewport) { // When viewport is default, intermediate textures created with this descriptor will have dynamic resolution enabled. baseCameraData.cameraTargetDescriptor.useDynamicScale = true; } else { // Some effects like Vignette computes aspect ratio from width and height. We have to take viewport into consideration if it is not default viewport. baseCameraData.cameraTargetDescriptor.width = baseCameraData.pixelWidth; baseCameraData.cameraTargetDescriptor.height = baseCameraData.pixelHeight; baseCameraData.cameraTargetDescriptor.useDynamicScale = false; } // If upscaling is active, set the scaled width and height baseCameraData.scaledWidth = Mathf.Max(1, (int)(baseCameraData.pixelWidth * baseCameraData.renderScale)); baseCameraData.scaledHeight = Mathf.Max(1, (int)(baseCameraData.pixelHeight * baseCameraData.renderScale)); } static void UpdateOffscreenUIRendering(UniversalCameraData cameraData, bool finalOutputHDR) { // The first eligible camera in the frame draws HDR offscreen overlay UI. var rendersOffscreenUI = cameraData.rendersOverlayUI && finalOutputHDR && !offscreenUIRenderedInCurrentFrame; if (rendersOffscreenUI) offscreenUIRenderedInCurrentFrame = true; cameraData.rendersOffscreenUI = rendersOffscreenUI; cameraData.blitsOffscreenUICover = rendersOffscreenUI && requireOffscreenUICoverPrepass; } static void UpdateVolumeFramework(Camera camera, UniversalAdditionalCameraData additionalCameraData) { using var profScope = new ProfilingScope(URPProfilingSamplers.UpdateVolumeFramework); // We update the volume framework for: // * All cameras in the editor when not in playmode // * scene cameras // * cameras with update mode set to EveryFrame // * cameras with update mode set to UsePipelineSettings and the URP Asset set to EveryFrame bool shouldUpdate = camera.cameraType == CameraType.SceneView; shouldUpdate |= additionalCameraData != null && additionalCameraData.requiresVolumeFrameworkUpdate; #if UNITY_EDITOR shouldUpdate |= Application.isPlaying == false; #endif // When we have volume updates per-frame disabled... if (!shouldUpdate && additionalCameraData) { // If an invalid volume stack is present, destroy it if (additionalCameraData.volumeStack != null && !additionalCameraData.volumeStack.isValid) { camera.DestroyVolumeStack(additionalCameraData); } // Create a local volume stack and cache the state if it's null if (additionalCameraData.volumeStack == null) { camera.UpdateVolumeStack(additionalCameraData); } VolumeManager.instance.stack = additionalCameraData.volumeStack; return; } // When we want to update the volumes every frame... // We destroy the volumeStack in the additional camera data, if present, to make sure // it gets recreated and initialized if the update mode gets later changed to ViaScripting... if (additionalCameraData && additionalCameraData.volumeStack != null) { camera.DestroyVolumeStack(additionalCameraData); } // Get the mask + trigger and update the stack camera.GetVolumeLayerMaskAndTrigger(additionalCameraData, out LayerMask layerMask, out Transform trigger); VolumeManager.instance.ResetMainStack(); VolumeManager.instance.Update(trigger, layerMask); } static bool CheckPostProcessForDepth(UniversalCameraData cameraData) { if (!cameraData.postProcessEnabled) return false; if (cameraData.IsTemporalAAEnabled() && (cameraData.renderType == CameraRenderType.Base)) return true; return CheckPostProcessForDepth(); } static bool CheckPostProcessForDepth() { var stack = VolumeManager.instance.stack; if (stack.GetComponent().IsActive()) return true; if (stack.GetComponent().IsActive()) return true; if (LensFlareCommonSRP.IsOcclusionRTCompatible() && !LensFlareCommonSRP.Instance.IsEmpty() && UniversalRenderPipeline.asset.supportDataDrivenLensFlare) return true; return false; } internal static void SetSupportedRenderingFeatures(UniversalRenderPipelineAsset pipelineAsset) { #if UNITY_EDITOR SupportedRenderingFeatures.active.reflectionProbeModes = SupportedRenderingFeatures.ReflectionProbeModes.Rotation; SupportedRenderingFeatures.active.defaultMixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeModes.Subtractive; SupportedRenderingFeatures.active.mixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeModes.Subtractive | SupportedRenderingFeatures.LightmapMixedBakeModes.IndirectOnly | SupportedRenderingFeatures.LightmapMixedBakeModes.Shadowmask; SupportedRenderingFeatures.active.lightmapBakeTypes = LightmapBakeType.Baked | LightmapBakeType.Mixed | LightmapBakeType.Realtime; SupportedRenderingFeatures.active.lightmapsModes = LightmapsMode.CombinedDirectional | LightmapsMode.NonDirectional; SupportedRenderingFeatures.active.lightProbeProxyVolumes = false; SupportedRenderingFeatures.active.motionVectors = true; SupportedRenderingFeatures.active.receiveShadows = false; SupportedRenderingFeatures.active.reflectionProbes = false; SupportedRenderingFeatures.active.reflectionProbesBlendDistance = true; SupportedRenderingFeatures.active.particleSystemInstancing = true; SupportedRenderingFeatures.active.overridesEnableLODCrossFade = true; SceneViewDrawMode.SetupDrawMode(); #endif if (GraphicsSettings.TryGetRenderPipelineSettings(out var reflectionProbeSettings)) { SupportedRenderingFeatures.active.reflectionProbeModes = reflectionProbeSettings.UseReflectionProbeRotation ? SupportedRenderingFeatures.ReflectionProbeModes.Rotation : SupportedRenderingFeatures.ReflectionProbeModes.None; } SupportedRenderingFeatures.active.supportsHDR = pipelineAsset != null && pipelineAsset.supportsHDR; SupportedRenderingFeatures.active.rendersUIOverlay = true; } static ScriptableRenderer GetRenderer(Camera camera, UniversalAdditionalCameraData additionalCameraData) { var renderer = additionalCameraData != null ? additionalCameraData.scriptableRenderer : null; if (renderer == null || camera.cameraType == CameraType.SceneView) renderer = asset.scriptableRenderer; return renderer; } internal static void InitializeScaledDimensions(Camera camera, UniversalCameraData cameraData) { cameraData.scaledWidth = Mathf.Max(1, (int) (camera.pixelWidth * cameraData.renderScale)); cameraData.scaledHeight = Mathf.Max(1, (int) (camera.pixelHeight * cameraData.renderScale)); } #if ENABLE_UPSCALER_FRAMEWORK // Whether the active upscaler drives this camera's render resolution. False for editor cameras (scene/preview/ // reflection would get resized to the upscaler's resolution) and hardware-DRS cameras (ScalableBufferManager // owns scaling and is mutually exclusive with the framework upscaler; see UpscalerPostProcessPass). internal static bool UpscalerDrivesCameraResolution(UniversalCameraData cameraData, IUpscaler activeUpscaler) { if (activeUpscaler == null) return false; var cameraType = cameraData.camera.cameraType; if (cameraType == CameraType.SceneView || cameraType == CameraType.Preview || cameraType == CameraType.Reflection) return false; return !cameraData.camera.allowDynamicResolution; } // Determines whether the active upscaler dictates the render resolution for this camera. // Returns false (the pipeline keeps its Render Scale) when: // - no upscaler is active, or the camera is an editor (scene/preview/reflection) camera, or // - the upscaler does not pin the resolution (constraint != Fixed, e.g. STP/spatial filters, or range-driven custom scaling). // A quality-mode upscaler that renders at native (e.g. DLSS DLAA) still dictates (qualityModeResolution == display), // so it correctly forces native regardless of Render Scale. internal static bool TryGetUpscalerDictatedResolution(UniversalCameraData cameraData, IUpscaler activeUpscaler, out Vector2Int renderResolution) { renderResolution = default; if (!UpscalerDrivesCameraResolution(cameraData, activeUpscaler)) return false; // Use cameraData.pixelWidth/Height (the viewport size, XR-adjusted) rather than camera.pixelWidth, to match // the display resolution the rest of the pipeline scales from. var displayResolution = new Vector2Int(cameraData.pixelWidth, cameraData.pixelHeight); var info = activeUpscaler.GetResolutionInfo(displayResolution, upscaling.GetGlobalOptions(activeUpscaler)); if (info.constraint != UpscalerResolutionConstraint.Fixed) return false; renderResolution = info.qualityModeResolution; return true; } // Last requested Render Scale (rounded to 2 decimals) we warned about; NaN means none. Tracked so a clamp warns // once per distinct out-of-range value instead of every frame. float m_LastWarnedClampRequestedScale = float.NaN; // Clamps a pipeline-chosen render size to the active upscaler's supported [min,max] range, for upscalers that // constrain the resolution without dictating it (constraint == Range). Returns the // size unchanged when the range doesn't apply, and warns when a clamp occurs. internal static Vector2Int ClampRenderSizeToUpscalerBounds(UniversalCameraData cameraData, IUpscaler activeUpscaler, Vector2Int renderSize) { if (!UpscalerDrivesCameraResolution(cameraData, activeUpscaler)) return renderSize; // In upscaler terms we have two resolutions: Render Resolution and Display Resolution. // In this context, the target(display resolution) is the camera's output viewport. // The local name deliberately mirrors the IUpscaler API parameters. var displayResolution = new Vector2Int(cameraData.pixelWidth, cameraData.pixelHeight); // Only upscalers actually upscale (render size below the display). At render scale >= 1.0 the image is native // or supersampled (downscaled) and the upscaler is inactive, so its range doesn't apply — don't clamp (it // would corrupt the supersampling resolution) and don't warn. if (renderSize.x >= displayResolution.x && renderSize.y >= displayResolution.y) return renderSize; var info = activeUpscaler.GetResolutionInfo(displayResolution, upscaling.GetGlobalOptions(activeUpscaler)); if (info.constraint != UpscalerResolutionConstraint.Range) return renderSize; var clamped = new Vector2Int( Mathf.Clamp(renderSize.x, info.minResolution.x, info.maxResolution.x), Mathf.Clamp(renderSize.y, info.minResolution.y, info.maxResolution.y)); if (Debug.isDebugBuild && RenderPipelineManager.currentPipeline is UniversalRenderPipeline urp) { if (clamped == renderSize) { urp.m_LastWarnedClampRequestedScale = float.NaN; // in range: clear it so a later out-of-range value warns again } else { // A clamp occurred: warn once per distinct requested Render Scale float requestedScale = (float)renderSize.x / displayResolution.x; float roundedRequested = Mathf.Floor(requestedScale * 100f + 0.5f) / 100f; if (!Mathf.Approximately(urp.m_LastWarnedClampRequestedScale, roundedRequested)) { urp.m_LastWarnedClampRequestedScale = roundedRequested; float minScale = (float)info.minResolution.x / displayResolution.x; float maxScale = (float)info.maxResolution.x / displayResolution.x; Debug.LogWarning( $"Requested Render Scale {requestedScale:0.00} is outside {activeUpscaler.name}'s supported range " + $"[{minScale:0.00}..{maxScale:0.00}] and was clamped. Keep Render Scale / dynamic resolution within " + "the upscaler's limits to avoid this."); } } } return clamped; } #endif static UniversalCameraData CreateCameraData(ContextContainer frameData, Camera camera, UniversalAdditionalCameraData additionalCameraData) { using var profScope = new ProfilingScope(Profiling.Pipeline.initializeCameraData); var renderer = GetRenderer(camera, additionalCameraData); UniversalCameraData cameraData = frameData.Create(); cameraData.renderer = renderer; InitializeStackedCameraData(camera, additionalCameraData, cameraData); cameraData.camera = camera; // Add reference to writable camera history to give access to injected user render passes which can produce history. cameraData.historyManager = additionalCameraData?.historyManager; /////////////////////////////////////////////////////////////////// // Descriptor settings / /////////////////////////////////////////////////////////////////// // If upscaling is active, set the scaled width and height InitializeScaledDimensions(camera, cameraData); #if ENABLE_UPSCALER_FRAMEWORK IUpscaler activeUpscaler = upscaling.activeUpscaler; bool upscalerDictatesResolution = TryGetUpscalerDictatedResolution(cameraData, activeUpscaler, out var upscalerRenderSize); if (upscalerDictatesResolution) { cameraData.scaledWidth = upscalerRenderSize.x; cameraData.scaledHeight = upscalerRenderSize.y; } else { // Upscaler doesn't dictate the resolution but may constrain it to a supported range; clamp into it. var clamped = ClampRenderSizeToUpscalerBounds(cameraData, activeUpscaler, new Vector2Int(cameraData.scaledWidth, cameraData.scaledHeight)); cameraData.scaledWidth = clamped.x; cameraData.scaledHeight = clamped.y; } // Capture the global hardware-DRS (ScalableBufferManager) scale once here so upscaler passes read this stable // per-camera value rather than the live global, which another pass could mutate mid-frame. cameraData.hardwareDynamicResolutionScale = camera.allowDynamicResolution ? new Vector2(ScalableBufferManager.widthScaleFactor, ScalableBufferManager.heightScaleFactor) : Vector2.one; #endif #if ENABLE_MULTI_WINDOWING && PLATFORM_SUPPORTS_PER_WINDOW_TRANSPARENCY && !UNITY_EDITOR bool needsAlphaChannel = GameWindowManager.IsGameWindowTransparent(cameraData.camera.targetDisplay); #else bool needsAlphaChannel = Graphics.preserveFramebufferAlpha; #endif cameraData.hdrColorBufferPrecision = asset ? asset.hdrColorBufferPrecision : HDRColorBufferPrecision._32Bits; CreateBackbufferInfo(camera, cameraData, needsAlphaChannel); int msaaSamples = GetIntermediateTexturesMSAA(cameraData, renderer); cameraData.cameraTargetDescriptor = CreateRenderTextureDescriptor(camera, cameraData, msaaSamples); uint count = GraphicsFormatUtility.GetAlphaComponentCount(cameraData.cameraTargetDescriptor.graphicsFormat); cameraData.isAlphaOutputEnabled = GraphicsFormatUtility.HasAlphaChannel(cameraData.cameraTargetDescriptor.graphicsFormat); if (cameraData.camera.cameraType == CameraType.SceneView && CoreUtils.IsSceneFilteringEnabled()) cameraData.isAlphaOutputEnabled = true; return cameraData; } /// /// Initialize camera data settings common for all cameras in the stack. Overlay cameras will inherit /// settings from base camera. /// /// Base camera to inherit settings from. /// Component that contains additional base camera data. /// Camera data to initialize setttings. static void InitializeStackedCameraData(Camera baseCamera, UniversalAdditionalCameraData baseAdditionalCameraData, UniversalCameraData cameraData) { using var profScope = new ProfilingScope(Profiling.Pipeline.initializeStackedCameraData); var settings = asset; cameraData.targetTexture = baseCamera.targetTexture; cameraData.cameraType = baseCamera.cameraType; bool isSceneViewCamera = cameraData.isSceneViewCamera; /////////////////////////////////////////////////////////////////// // Environment and Post-processing settings / /////////////////////////////////////////////////////////////////// if (isSceneViewCamera) { cameraData.volumeLayerMask = 1; // "Default" cameraData.volumeTrigger = null; cameraData.isStopNaNEnabled = false; cameraData.isDitheringEnabled = false; cameraData.antialiasing = AntialiasingMode.None; cameraData.antialiasingQuality = AntialiasingQuality.High; cameraData.xrRendering = false; cameraData.allowHDROutput = false; } else if (baseAdditionalCameraData != null) { cameraData.volumeLayerMask = baseAdditionalCameraData.volumeLayerMask; cameraData.volumeTrigger = baseAdditionalCameraData.volumeTrigger == null ? baseCamera.transform : baseAdditionalCameraData.volumeTrigger; cameraData.isStopNaNEnabled = baseAdditionalCameraData.stopNaN && SystemInfo.graphicsShaderLevel >= 35; cameraData.isDitheringEnabled = baseAdditionalCameraData.dithering; cameraData.antialiasing = baseAdditionalCameraData.antialiasing; cameraData.antialiasingQuality = baseAdditionalCameraData.antialiasingQuality; cameraData.xrRendering = baseAdditionalCameraData.allowXRRendering && XRSystem.displayActive; cameraData.allowHDROutput = baseAdditionalCameraData.allowHDROutput; } else { cameraData.volumeLayerMask = 1; // "Default" cameraData.volumeTrigger = null; cameraData.isStopNaNEnabled = false; cameraData.isDitheringEnabled = false; cameraData.antialiasing = AntialiasingMode.None; cameraData.antialiasingQuality = AntialiasingQuality.High; cameraData.xrRendering = XRSystem.displayActive; cameraData.allowHDROutput = true; } var supportedRenderingFeatures = cameraData.renderer.supportedRenderingFeatures; /////////////////////////////////////////////////////////////////// // Settings that control output of the camera / /////////////////////////////////////////////////////////////////// if (!supportedRenderingFeatures.antiAliasing) cameraData.antialiasing = AntialiasingMode.None; cameraData.isHdrEnabled = baseCamera.allowHDR && settings.supportsHDR && supportedRenderingFeatures.supportsHDR; cameraData.allowHDROutput &= settings.supportsHDR; Rect cameraRect = baseCamera.rect; cameraData.pixelRect = baseCamera.pixelRect; cameraData.pixelWidth = baseCamera.pixelWidth; cameraData.pixelHeight = baseCamera.pixelHeight; cameraData.aspectRatio = (float)cameraData.pixelWidth / (float)cameraData.pixelHeight; cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f || Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f)); bool isScenePreviewOrReflectionCamera = cameraData.cameraType == CameraType.SceneView || cameraData.cameraType == CameraType.Preview || cameraData.cameraType == CameraType.Reflection; bool isGameCamera = !isScenePreviewOrReflectionCamera; // Discard variations lesser than kRenderScaleThreshold. // Scale is only enabled for gameview. const float kRenderScaleThreshold = 0.05f; bool disableRenderScale = (Mathf.Abs(1.0f - settings.renderScale) < kRenderScaleThreshold) || isScenePreviewOrReflectionCamera || !supportedRenderingFeatures.upscaling; cameraData.renderScale = disableRenderScale? 1.0f : settings.renderScale; #if ENABLE_UPSCALER_FRAMEWORK // ImageUpscalingFilter is deprecated, we now track by upscaler name string resolvedUpscalerName = ResolveUpscalingFilterSelection(cameraData.pixelWidth, cameraData.pixelHeight, cameraData.renderScale, settings.upscalerName); cameraData.resolvedUpscalerHash = Shader.PropertyToID(resolvedUpscalerName); // now that we've deprecated ImageUpscalingFilter, check with the builtin upscaler names IUpscaler activeUpscaler = upscaling.activeUpscaler; bool upscalerSupportsTemporalAntiAliasing = activeUpscaler != null && activeUpscaler.isTemporal; bool upscalerSupportsSharpening = activeUpscaler != null && activeUpscaler.supportsSharpening; #else // Convert the upscaling filter selection from the pipeline asset into an image upscaling filter cameraData.upscalingFilter = supportedRenderingFeatures.upscaling? ResolveUpscalingFilterSelection(new Vector2(cameraData.pixelWidth, cameraData.pixelHeight), cameraData.renderScale, settings.upscalingFilter) : ImageUpscalingFilter.Point; bool upscalerSupportsTemporalAntiAliasing = cameraData.upscalingFilter == ImageUpscalingFilter.STP; bool upscalerSupportsSharpening = cameraData.upscalingFilter == ImageUpscalingFilter.FSR; #endif if (cameraData.renderScale > 1.0f) { cameraData.imageScalingMode = ImageScalingMode.Downscaling; } else if ( (cameraData.renderScale < 1.0f) || (isGameCamera && (upscalerSupportsTemporalAntiAliasing || upscalerSupportsSharpening)) ) { // When certain upscalers are requested, we still consider 100% render scale an upscaling operation. (This behavior is only intended for game view cameras) // This allows us to run the upscaling shader passes all the time since they improve visual quality even at 100% scale. cameraData.imageScalingMode = ImageScalingMode.Upscaling; // We force temporal anti-aliasing on when it's a prerequisite. if (upscalerSupportsTemporalAntiAliasing) { cameraData.antialiasing = AntialiasingMode.TemporalAntiAliasing; } } else { cameraData.imageScalingMode = ImageScalingMode.None; } cameraData.fsrOverrideSharpness = settings.fsrOverrideSharpness; cameraData.fsrSharpness = settings.fsrSharpness; cameraData.xr = XRSystem.emptyPass; var renderScaleXR = cameraData.renderScale; XRSystem.SetRenderScale(renderScaleXR); var commonOpaqueFlags = SortingCriteria.CommonOpaque; var noFrontToBackOpaqueFlags = SortingCriteria.SortingLayer | SortingCriteria.RenderQueue | SortingCriteria.OptimizeStateChanges | SortingCriteria.CanvasOrder; bool hasHSRGPU = SystemInfo.hasHiddenSurfaceRemovalOnGPU; bool canSkipFrontToBackSorting = (baseCamera.opaqueSortMode == OpaqueSortMode.Default && hasHSRGPU) || baseCamera.opaqueSortMode == OpaqueSortMode.NoDistanceSort; cameraData.defaultOpaqueSortFlags = canSkipFrontToBackSorting ? noFrontToBackOpaqueFlags : commonOpaqueFlags; cameraData.captureActions = Unity.RenderPipelines.Core.Runtime.Shared.CameraCaptureBridge.GetCachedCaptureActionsEnumerator(baseCamera); } /// /// Initialize settings that can be different for each camera in the stack. /// /// Camera to initialize settings from. /// Additional camera data component to initialize settings from. /// True if this is the last camera in the stack and rendering should resolve to camera target. /// True if the base camera is the last base camera. /// Settings to be initilized. static void InitializeAdditionalCameraData(Camera camera, UniversalAdditionalCameraData additionalCameraData, bool resolveFinalTarget, bool isLastBaseCamera, UniversalCameraData cameraData) { using var profScope = new ProfilingScope(Profiling.Pipeline.initializeAdditionalCameraData); var renderer = GetRenderer(camera, additionalCameraData); var settings = asset; bool anyShadowsEnabled = settings.supportsMainLightShadows || settings.supportsAdditionalLightShadows; cameraData.maxShadowDistance = Mathf.Min(settings.shadowDistance, camera.farClipPlane); cameraData.maxShadowDistance = (anyShadowsEnabled && cameraData.maxShadowDistance >= camera.nearClipPlane) ? cameraData.maxShadowDistance : 0.0f; bool isSceneViewCamera = cameraData.isSceneViewCamera; if (isSceneViewCamera) { cameraData.renderType = CameraRenderType.Base; cameraData.clearDepth = true; cameraData.postProcessEnabled = CoreUtils.ArePostProcessesEnabled(camera); cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture; cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture; cameraData.useScreenCoordOverride = false; cameraData.screenSizeOverride = cameraData.pixelRect.size; cameraData.screenCoordScaleBias = Vector2.one; } else if (additionalCameraData != null) { cameraData.renderType = additionalCameraData.renderType; cameraData.clearDepth = (additionalCameraData.renderType != CameraRenderType.Base) ? additionalCameraData.clearDepth : true; cameraData.postProcessEnabled = additionalCameraData.renderPostProcessing; cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f; cameraData.requiresDepthTexture = additionalCameraData.requiresDepthTexture; cameraData.requiresOpaqueTexture = additionalCameraData.requiresColorTexture; cameraData.useScreenCoordOverride = additionalCameraData.useScreenCoordOverride; cameraData.screenSizeOverride = additionalCameraData.screenSizeOverride; cameraData.screenCoordScaleBias = additionalCameraData.screenCoordScaleBias; } else { cameraData.renderType = CameraRenderType.Base; cameraData.clearDepth = true; cameraData.postProcessEnabled = false; cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture; cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture; cameraData.useScreenCoordOverride = false; cameraData.screenSizeOverride = cameraData.pixelRect.size; cameraData.screenCoordScaleBias = Vector2.one; } var supportedRenderingFeatures = renderer.supportedRenderingFeatures; if (!supportedRenderingFeatures.cameraOpaqueTexture) cameraData.requiresOpaqueTexture = false; if (!supportedRenderingFeatures.cameraDepthTexture) cameraData.requiresDepthTexture = false; cameraData.renderer = renderer; cameraData.postProcessingRequiresDepthTexture = CheckPostProcessForDepth(cameraData); cameraData.resolveFinalTarget = resolveFinalTarget; cameraData.isLastBaseCamera = isLastBaseCamera; // enable GPU occlusion culling in game and scene views only cameraData.useGPUOcclusionCulling = GPUResidentDrawer.IsInstanceOcclusionCullingEnabled() && renderer.supportsGPUOcclusion && camera.cameraType is CameraType.SceneView or CameraType.Game or CameraType.Preview; cameraData.requiresDepthTexture |= cameraData.useGPUOcclusionCulling; // Disable depth and color copy. We should add it in the renderer instead to avoid performance pitfalls // of camera stacking breaking render pass execution implicitly. bool isOverlayCamera = (cameraData.renderType == CameraRenderType.Overlay); if (isOverlayCamera) { cameraData.requiresOpaqueTexture = false; } // NOTE: TAA depends on XR modifications of cameraTargetDescriptor. if (additionalCameraData != null) UpdateTemporalAAData(cameraData, additionalCameraData); Matrix4x4 projectionMatrix = camera.projectionMatrix; // Overlay cameras inherit viewport from base. // If the viewport is different between them we might need to patch the projection to adjust aspect ratio // matrix to prevent squishing when rendering objects in overlay cameras. if (isOverlayCamera && !camera.orthographic && cameraData.pixelRect != camera.pixelRect) { // m00 = (cotangent / aspect), therefore m00 * aspect gives us cotangent. float cotangent = camera.projectionMatrix.m00 * camera.aspect; // Get new m00 by dividing by base camera aspectRatio. float newCotangent = cotangent / cameraData.aspectRatio; projectionMatrix.m00 = newCotangent; } // TAA debug settings // Affects the jitter set just below. Do not move. ApplyTaaRenderingDebugOverrides(ref cameraData.taaSettings); Matrix4x4 jitterMat = Matrix4x4.identity; // Depends on the cameraTargetDesc, size and MSAA also XR modifications of those. #if ENABLE_UPSCALER_FRAMEWORK IUpscaler activeUpscaler = upscaling.activeUpscaler; if (cameraData.IsTemporalAAEnabled() && activeUpscaler != null) { // Upscalers compute jitter with resolution-dependent parameters. int taaFrameIndex = TemporalAA.CalculateTaaFrameIndex(ref cameraData.taaSettings); float actualWidth = cameraData.cameraTargetDescriptor.width; float upscaleRatio = (float)cameraData.pixelWidth / actualWidth; activeUpscaler.CalculateJitter(taaFrameIndex, upscaleRatio, out Vector2 subpixelJitter, out bool allowScaling); // Note: DLSS/FSR2 set allowScaling=false, so jitterScale is not applied if (allowScaling) subpixelJitter *= cameraData.taaSettings.jitterScale; // Store for later use by upscaler during post-process cameraData.subpixelJitter = subpixelJitter; jitterMat = TemporalAA.CalculateJitterMatrix(cameraData, subpixelJitter); } else #endif if (cameraData.IsSTPEnabled()) { jitterMat = TemporalAA.CalculateJitterMatrix(cameraData, StpUtils.s_JitterFunc); } else { jitterMat = TemporalAA.CalculateJitterMatrix(cameraData, TemporalAA.s_JitterFunc); } cameraData.SetViewProjectionAndJitterMatrix(camera.worldToCameraMatrix, projectionMatrix, jitterMat); cameraData.worldSpaceCameraPos = camera.transform.position; var backgroundColorSRGB = camera.backgroundColor; // Get the background color from preferences if preview camera #if UNITY_EDITOR if (camera.cameraType == CameraType.Preview && camera.clearFlags != CameraClearFlags.SolidColor) { backgroundColorSRGB = CoreRenderPipelinePreferences.previewBackgroundColor; } #endif cameraData.backgroundColor = CoreUtils.ConvertSRGBToActiveColorSpace(backgroundColorSRGB); cameraData.stackAnyPostProcessingEnabled = cameraData.postProcessEnabled; cameraData.stackLastCameraOutputToHDR = cameraData.isHDROutputActive; // Apply post-processing settings to the alpha output. // cameraData.isAlphaOutputEnabled is set based on target alpha channel availability on create. Target can be a RenderTexture or the back-buffer. bool allowAlphaOutput = !cameraData.postProcessEnabled || (cameraData.postProcessEnabled && settings.allowPostProcessAlphaOutput); cameraData.isAlphaOutputEnabled = cameraData.isAlphaOutputEnabled && allowAlphaOutput; } static UniversalRenderingData CreateRenderingData(ContextContainer frameData, UniversalRenderPipelineAsset settings, CommandBuffer cmd, RenderingMode? renderingMode, ScriptableRenderer renderer) { UniversalLightData universalLightData = frameData.Get(); UniversalRenderingData data = frameData.Get(); data.perObjectData = GetPerObjectLightFlags(universalLightData, settings, renderingMode); UniversalRenderer universalRenderer = renderer as UniversalRenderer; if (universalRenderer != null) { data.renderingMode = universalRenderer.renderingModeActual; data.prepassLayerMask = universalRenderer.prepassLayerMask; data.opaqueLayerMask = universalRenderer.opaqueLayerMask; data.transparentLayerMask = universalRenderer.transparentLayerMask; } data.stencilLodCrossFadeEnabled = settings.enableLODCrossFade && settings.lodCrossFadeDitheringType == LODCrossFadeDitheringType.Stencil; return data; } static UniversalShadowData CreateShadowData(ContextContainer frameData, UniversalRenderPipelineAsset urpAsset, RenderingMode? renderingMode) { using var profScope = new ProfilingScope(Profiling.Pipeline.initializeShadowData); // Initial setup // ------------------------------------------------------ UniversalShadowData shadowData = frameData.Create(); UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); s_ShadowBiasData.Clear(); s_ShadowResolutionData.Clear(); shadowData.shadowmapDepthBufferBits = 16; shadowData.mainLightShadowCascadeBorder = urpAsset.cascadeBorder; shadowData.mainLightShadowCascadesCount = urpAsset.shadowCascadeCount; shadowData.mainLightShadowCascadesSplit = GetMainLightCascadeSplit(shadowData.mainLightShadowCascadesCount, urpAsset); shadowData.mainLightShadowmapWidth = urpAsset.mainLightShadowmapResolution; shadowData.mainLightShadowmapHeight = urpAsset.mainLightShadowmapResolution; shadowData.additionalLightsShadowmapWidth = shadowData.additionalLightsShadowmapHeight = Math.Min(urpAsset.additionalLightsShadowmapResolution, PlatformAutoDetect.maxSupportedShadowAtlasResolution); // This will be setup in AdditionalLightsShadowCasterPass. shadowData.isKeywordAdditionalLightShadowsEnabled = false; shadowData.isKeywordSoftShadowsEnabled = false; // Those fields must be setup after ApplyAdaptivePerformance is called on RenderingData. // This is because this function can currently modify mainLightShadowmapWidth, mainLightShadowmapHeight and mainLightShadowCascadesCount. // All three parameters are needed to compute those fields, so their initialization is deferred to InitializeMainLightShadowResolution. shadowData.mainLightShadowResolution = 0; shadowData.mainLightRenderTargetWidth = 0; shadowData.mainLightRenderTargetHeight = 0; // Those two fields must be initialized using ShadowData, which can be modified right after this function (InitializeRenderingData) by ApplyAdaptivePerformance. // Their initializations is thus deferred to a later point when ShadowData is fully initialized. shadowData.shadowAtlasLayout = default; shadowData.visibleLightsShadowCullingInfos = default; // Setup data that requires iterating over lights // ------------------------------------------------------ var mainLightIndex = lightData.mainLightIndex; var visibleLights = lightData.visibleLights; // maxShadowDistance is set to 0.0f when the Render Shadows toggle is disabled on the camera bool cameraRenderShadows = cameraData.maxShadowDistance > 0.0f; shadowData.mainLightShadowsEnabled = urpAsset.supportsMainLightShadows && urpAsset.mainLightRenderingMode == LightRenderingMode.PerPixel; shadowData.supportsMainLightShadows = SystemInfo.supportsShadows && shadowData.mainLightShadowsEnabled && cameraRenderShadows; bool isForwardPlus = renderingMode.HasValue ? renderingMode.Value == RenderingMode.ForwardPlus : false; shadowData.additionalLightShadowsEnabled = urpAsset.supportsAdditionalLightShadows && (urpAsset.additionalLightsRenderingMode == LightRenderingMode.PerPixel || isForwardPlus); shadowData.supportsAdditionalLightShadows = SystemInfo.supportsShadows && shadowData.additionalLightShadowsEnabled && !lightData.shadeAdditionalLightsPerVertex && cameraRenderShadows; // Early out if shadows are not rendered... if (!shadowData.supportsMainLightShadows && !shadowData.supportsAdditionalLightShadows) return shadowData; shadowData.supportsMainLightShadows &= mainLightIndex != -1 && visibleLights[mainLightIndex].light != null && visibleLights[mainLightIndex].light.shadows != LightShadows.None; if (shadowData.supportsAdditionalLightShadows) { // Check if there is at least one additional light casting shadows... bool additionalLightsCastShadows = false; for (int i = 0; i < visibleLights.Length; ++i) { if (i == mainLightIndex) continue; ref VisibleLight vl = ref visibleLights.UnsafeElementAtMutable(i); // UniversalRP doesn't support additional directional light shadows yet if (vl.lightType == LightType.Spot || vl.lightType == LightType.Point) { Light light = vl.light; if (light == null || light.shadows == LightShadows.None) continue; additionalLightsCastShadows = true; break; } } shadowData.supportsAdditionalLightShadows &= additionalLightsCastShadows; } // Check again if it's possible to early out... if (!shadowData.supportsMainLightShadows && !shadowData.supportsAdditionalLightShadows) return shadowData; for (int i = 0; i < visibleLights.Length; ++i) { if (!shadowData.supportsMainLightShadows && i == mainLightIndex) { s_ShadowBiasData.Add(Vector4.zero); s_ShadowResolutionData.Add(0); continue; } if (!shadowData.supportsAdditionalLightShadows && i != mainLightIndex) { s_ShadowBiasData.Add(Vector4.zero); s_ShadowResolutionData.Add(0); continue; } ref VisibleLight vl = ref visibleLights.UnsafeElementAtMutable(i); Light light = vl.light; UniversalAdditionalLightData data = null; if (light != null) { light.gameObject.TryGetComponent(out data); } if (data && !data.usePipelineSettings) s_ShadowBiasData.Add(new Vector4(light.shadowBias, light.shadowNormalBias, 0.0f, 0.0f)); else s_ShadowBiasData.Add(new Vector4(urpAsset.shadowDepthBias, urpAsset.shadowNormalBias, 0.0f, 0.0f)); if (data && (data.additionalLightsShadowResolutionTier == UniversalAdditionalLightData.AdditionalLightsShadowResolutionTierCustom)) { s_ShadowResolutionData.Add((int)light.shadowResolution); // native code does not clamp light.shadowResolution between -1 and 3 } else if (data && (data.additionalLightsShadowResolutionTier != UniversalAdditionalLightData.AdditionalLightsShadowResolutionTierCustom)) { int resolutionTier = Mathf.Clamp(data.additionalLightsShadowResolutionTier, UniversalAdditionalLightData.AdditionalLightsShadowResolutionTierLow, UniversalAdditionalLightData.AdditionalLightsShadowResolutionTierHigh); s_ShadowResolutionData.Add(urpAsset.GetAdditionalLightsShadowResolution(resolutionTier)); } else { s_ShadowResolutionData.Add(urpAsset.GetAdditionalLightsShadowResolution(UniversalAdditionalLightData.AdditionalLightsShadowDefaultResolutionTier)); } } shadowData.bias = s_ShadowBiasData; shadowData.resolution = s_ShadowResolutionData; shadowData.supportsSoftShadows = urpAsset.supportsSoftShadows && (shadowData.supportsMainLightShadows || shadowData.supportsAdditionalLightShadows); return shadowData; } static CullContextData CreateCullContextData(ContextContainer frameData, ScriptableRenderContext context) { var cullData = frameData.Create(); cullData.SetRenderContext(context); return cullData; } private static Vector3 GetMainLightCascadeSplit(int mainLightShadowCascadesCount, UniversalRenderPipelineAsset urpAsset) { switch (mainLightShadowCascadesCount) { case 1: return new Vector3(1.0f, 0.0f, 0.0f); case 2: return new Vector3(urpAsset.cascade2Split, 1.0f, 0.0f); case 3: return urpAsset.cascade3Split; default: return urpAsset.cascade4Split; } } static void InitializeMainLightShadowResolution(UniversalShadowData shadowData) { int maxAtlasResolution = PlatformAutoDetect.maxSupportedShadowAtlasResolution; shadowData.mainLightShadowmapWidth = Mathf.Min(shadowData.mainLightShadowmapWidth, maxAtlasResolution); shadowData.mainLightShadowmapHeight = Mathf.Min(shadowData.mainLightShadowmapHeight, maxAtlasResolution); shadowData.mainLightShadowResolution = ShadowUtils.GetMaxTileResolutionInAtlas(shadowData.mainLightShadowmapWidth, shadowData.mainLightShadowmapHeight, shadowData.mainLightShadowCascadesCount); shadowData.mainLightRenderTargetWidth = shadowData.mainLightShadowmapWidth; shadowData.mainLightRenderTargetHeight = (shadowData.mainLightShadowCascadesCount == 2) ? shadowData.mainLightShadowmapHeight >> 1 : shadowData.mainLightShadowmapHeight; } static UniversalPostProcessingData CreatePostProcessingData(ContextContainer frameData, UniversalRenderPipelineAsset settings) { UniversalPostProcessingData postProcessingData = frameData.Create(); UniversalCameraData cameraData = frameData.Get(); postProcessingData.isEnabled = cameraData.postProcessEnabled; postProcessingData.gradingMode = settings.supportsHDR ? settings.colorGradingMode : ColorGradingMode.LowDynamicRange; if (cameraData.stackLastCameraOutputToHDR) postProcessingData.gradingMode = ColorGradingMode.HighDynamicRange; postProcessingData.lutSize = settings.colorGradingLutSize; postProcessingData.useFastSRGBLinearConversion = settings.useFastSRGBLinearConversion; postProcessingData.supportScreenSpaceLensFlare = settings.supportScreenSpaceLensFlare; postProcessingData.supportDataDrivenLensFlare = settings.supportDataDrivenLensFlare; #if ENABLE_UPSCALER_FRAMEWORK postProcessingData.activeUpscaler = upscaling.activeUpscaler; #endif return postProcessingData; } static UniversalResourceData CreateUniversalResourceData(ContextContainer frameData) { return frameData.Create(); } static UniversalLightData CreateLightData(ContextContainer frameData, UniversalRenderPipelineAsset settings, NativeArray visibleLights, RenderingMode? renderingMode) { using var profScope = new ProfilingScope(Profiling.Pipeline.initializeLightData); UniversalLightData lightData = frameData.Create(); lightData.visibleLights = visibleLights; lightData.mainLightIndex = GetMainLightIndex(settings, visibleLights); if (settings.additionalLightsRenderingMode != LightRenderingMode.Disabled) { lightData.additionalLightsCount = Math.Min((lightData.mainLightIndex != -1) ? visibleLights.Length - 1 : visibleLights.Length, maxVisibleAdditionalLights); lightData.maxPerObjectAdditionalLightsCount = Math.Min(settings.maxAdditionalLightsCount, maxPerObjectLights); } else { lightData.additionalLightsCount = 0; lightData.maxPerObjectAdditionalLightsCount = 0; } lightData.supportsAdditionalLights = settings.additionalLightsRenderingMode != LightRenderingMode.Disabled; lightData.shadeAdditionalLightsPerVertex = settings.additionalLightsRenderingMode == LightRenderingMode.PerVertex; lightData.supportsMixedLighting = settings.supportsMixedLighting; lightData.reflectionProbeBoxProjection = settings.reflectionProbeBoxProjection; lightData.supportsLightLayers = RenderingUtils.SupportsLightLayers(SystemInfo.graphicsDeviceType) && settings.useRenderingLayers; lightData.reflectionProbeBlending = settings.ShouldUseReflectionProbeBlending(); lightData.reflectionProbeAtlas = renderingMode.HasValue ? settings.ShouldUseReflectionProbeAtlasBlending(renderingMode.Value) : false; return lightData; } private static void ApplyTaaRenderingDebugOverrides(ref TemporalAA.Settings taaSettings) { var debugDisplaySettings = UniversalRenderPipelineDebugDisplaySettings.Instance; DebugDisplaySettingsRendering renderingSettings = debugDisplaySettings.renderingSettings; switch (renderingSettings.taaDebugMode) { case DebugDisplaySettingsRendering.TaaDebugMode.ShowClampedHistory: taaSettings.m_FrameInfluence = 0; break; case DebugDisplaySettingsRendering.TaaDebugMode.ShowRawFrame: taaSettings.m_FrameInfluence = 1; break; case DebugDisplaySettingsRendering.TaaDebugMode.ShowRawFrameNoJitter: taaSettings.m_FrameInfluence = 1; taaSettings.jitterScale = 0; break; } } private static void UpdateTemporalAAData(UniversalCameraData cameraData, UniversalAdditionalCameraData additionalCameraData) { // Always request the TAA history data here in order to fit the existing URP structure. additionalCameraData.historyManager.RequestAccess(); cameraData.taaHistory = additionalCameraData.historyManager.GetHistoryForWrite(); if (cameraData.IsSTPEnabled()) { additionalCameraData.historyManager.RequestAccess(); cameraData.stpHistory = additionalCameraData.historyManager.GetHistoryForWrite(); } // Update TAA settings ref var taaSettings = ref additionalCameraData.taaSettings; cameraData.taaSettings = taaSettings; // Decrease history clear counter. Typically clear is only 1 frame, but can be many for XR multipass eyes! taaSettings.resetHistoryFrames -= taaSettings.resetHistoryFrames > 0 ? 1 : 0; } private static void UpdateTemporalAATargets(UniversalCameraData cameraData) { if (cameraData.IsTemporalAAEnabled()) { bool xrMultipassEnabled = false; #if ENABLE_VR && ENABLE_XR_MODULE xrMultipassEnabled = cameraData.xr.enabled && !cameraData.xr.singlePassEnabled; #endif bool allocation; if (cameraData.IsSTPRequested()) { Debug.Assert(cameraData.stpHistory != null); // When STP is active, we don't require the full set of resources needed by TAA. cameraData.taaHistory.Reset(); allocation = cameraData.stpHistory.Update(cameraData); } else { allocation = cameraData.taaHistory.Update(cameraData, xrMultipassEnabled); } // Fill new history with current frame // XR Multipass renders a "frame" per eye if (allocation) cameraData.taaSettings.resetHistoryFrames += xrMultipassEnabled ? 2 : 1; } else { cameraData.taaHistory.Reset(); // TAA GPUResources is explicitly released if the feature is turned off. We could refactor this to rely on the type request and the "gc" only. // In the case where STP is requested, but TAA gets disabled for various reasons so STP is disabled, we should release the STP history resources if (cameraData.IsSTPRequested()) cameraData.stpHistory?.Reset(); } } static void UpdateCameraStereoMatrices(Camera camera, XRPass xr) { #if ENABLE_VR && ENABLE_XR_MODULE if (xr.enabled) { if (xr.singlePassEnabled) { for (int i = 0; i < Mathf.Min(2, xr.viewCount); i++) { camera.SetStereoProjectionMatrix((Camera.StereoscopicEye)i, xr.GetProjMatrix(i)); camera.SetStereoViewMatrix((Camera.StereoscopicEye)i, xr.GetViewMatrix(i)); } } else { camera.SetStereoProjectionMatrix((Camera.StereoscopicEye)xr.multipassId, xr.GetProjMatrix(0)); camera.SetStereoViewMatrix((Camera.StereoscopicEye)xr.multipassId, xr.GetViewMatrix(0)); } } #endif } static PerObjectData GetPerObjectLightFlags(UniversalLightData universalLightData, UniversalRenderPipelineAsset settings, RenderingMode? renderingMode) { using var profScope = new ProfilingScope(Profiling.Pipeline.getPerObjectLightFlags); bool useReflectionProbeBlending = settings.ShouldUseReflectionProbeBlending(); bool isForwardPlus = false; if (renderingMode.HasValue) isForwardPlus = renderingMode.Value == RenderingMode.ForwardPlus; var configuration = PerObjectData.Lightmaps | PerObjectData.LightProbe | PerObjectData.OcclusionProbe | PerObjectData.ShadowMask; if (!isForwardPlus) { configuration |= PerObjectData.ReflectionProbes | PerObjectData.LightData; } else if (!useReflectionProbeBlending) { configuration |= PerObjectData.ReflectionProbes; } if (universalLightData.additionalLightsCount > 0 && !isForwardPlus) { // In this case we also need per-object indices (unity_LightIndices) if (!RenderingUtils.useStructuredBuffer) configuration |= PerObjectData.LightIndices; } return configuration; } static int GetBrightestDirectionalLightIndex(UniversalRenderPipelineAsset settings, NativeArray visibleLights) { Light sunLight = RenderSettings.sun; int brightestDirectionalLightIndex = -1; float brightestLightIntensity = 0.0f; int totalVisibleLights = visibleLights.Length; for (int i = 0; i < totalVisibleLights; ++i) { ref VisibleLight currVisibleLight = ref visibleLights.UnsafeElementAtMutable(i); Light currLight = currVisibleLight.light; // Particle system lights have the light property as null. We sort lights so all particles lights // come last. Therefore, if first light is particle light then all lights are particle lights. // In this case we either have no main light or already found it. if (currLight == null) break; if (currVisibleLight.lightType == LightType.Directional) { // Sun source needs be a directional light if (currLight == sunLight) return i; // In case no sun light is present we will return the brightest directional light if (currLight.intensity > brightestLightIntensity) { brightestLightIntensity = currLight.intensity; brightestDirectionalLightIndex = i; } } } return brightestDirectionalLightIndex; } // Main Light is always a directional light static int GetMainLightIndex(UniversalRenderPipelineAsset settings, NativeArray visibleLights) { using var profScope = new ProfilingScope(Profiling.Pipeline.getMainLightIndex); int totalVisibleLights = visibleLights.Length; if (totalVisibleLights == 0 || settings.mainLightRenderingMode != LightRenderingMode.PerPixel) return -1; return GetBrightestDirectionalLightIndex(settings, visibleLights); } void SetupPerFrameShaderConstants() { using var profScope = new ProfilingScope(Profiling.Pipeline.setupPerFrameShaderConstants); // Required for 2D Unlit Shadergraph master node as it doesn't currently support hidden properties. Shader.SetGlobalColor(ShaderPropertyId.rendererColor, Color.white); Texture2D ditheringTexture = null; switch (asset.lodCrossFadeDitheringType) { case LODCrossFadeDitheringType.BayerMatrix: ditheringTexture = runtimeTextures.bayerMatrixTex; break; case LODCrossFadeDitheringType.BlueNoise: ditheringTexture = runtimeTextures.blueNoise64LTex; break; case LODCrossFadeDitheringType.Stencil: ditheringTexture = runtimeTextures.stencilDitherTex; // For the pass that has no stencil such as shadow and motion vector break; default: Debug.LogWarning($"This Lod Cross Fade Dithering Type is not supported: {asset.lodCrossFadeDitheringType}"); break; } if (ditheringTexture != null) { Shader.SetGlobalFloat(ShaderPropertyId.ditheringTextureInvSize, 1.0f / ditheringTexture.width); Shader.SetGlobalTexture(ShaderPropertyId.ditheringTexture, ditheringTexture); } } static void SetupPerCameraShaderConstants(CommandBuffer cmd) { using var profScope = new ProfilingScope(Profiling.Pipeline.setupPerCameraShaderConstants); // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe; Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity; Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor); cmd.SetGlobalVector(ShaderPropertyId.glossyEnvironmentColor, glossyEnvColor); // Used as fallback cubemap for reflections cmd.SetGlobalTexture(ShaderPropertyId.glossyEnvironmentCubeMap, ReflectionProbe.defaultTexture); cmd.SetGlobalVector(ShaderPropertyId.glossyEnvironmentCubeMapHDR, ReflectionProbe.defaultTextureHDRDecodeValues); // Ambient cmd.SetGlobalVector(ShaderPropertyId.ambientSkyColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.ambientSkyColor)); cmd.SetGlobalVector(ShaderPropertyId.ambientEquatorColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.ambientEquatorColor)); cmd.SetGlobalVector(ShaderPropertyId.ambientGroundColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.ambientGroundColor)); // Used when subtractive mode is selected cmd.SetGlobalVector(ShaderPropertyId.subtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor)); } static void CheckAndApplyDebugSettings(ref RenderingData renderingData) { var debugDisplaySettings = UniversalRenderPipelineDebugDisplaySettings.Instance; ref CameraData cameraData = ref renderingData.cameraData; if (debugDisplaySettings.AreAnySettingsActive && !cameraData.isPreviewCamera) { DebugDisplaySettingsRendering renderingSettings = debugDisplaySettings.renderingSettings; int msaaSamples = cameraData.cameraTargetDescriptor.msaaSamples; if (!renderingSettings.enableMsaa) msaaSamples = 1; if (!renderingSettings.enableHDR) cameraData.isHdrEnabled = false; if (!debugDisplaySettings.IsPostProcessingAllowed) cameraData.postProcessEnabled = false; cameraData.hdrColorBufferPrecision = asset ? asset.hdrColorBufferPrecision : HDRColorBufferPrecision._32Bits; cameraData.cameraTargetDescriptor.graphicsFormat = MakeRenderTextureGraphicsFormat(cameraData.isHdrEnabled, cameraData.hdrColorBufferPrecision, true); cameraData.cameraTargetDescriptor.msaaSamples = msaaSamples; } } #if ENABLE_UPSCALER_FRAMEWORK /// /// Returns the best supported image upscaling filter name based on the provided upscaling filter selection /// /// Size of the final image /// Scale being applied to the final image size /// Upscaling filter name selected by the user /// Either the original filter provided, or the best replacement available static string ResolveUpscalingFilterSelection(float imageSizeX, float imageSizeY, float renderScale, string selection) { // Fall back to the automatic filter if the selected filter isn't supported on the current platform or rendering environment if ((selection == k_UpscalerName_FSR1 && !FSRUtils.IsSupported()) || (selection == k_UpscalerName_STP && !STP.IsSupported()) ) { selection = k_UpscalerName_Auto; } string resolvedUpscaler = selection; if (selection == k_UpscalerName_Auto) { float pixelScale = (1.0f / renderScale); bool isIntegerScale = Mathf.Approximately((pixelScale - Mathf.Floor(pixelScale)), 0.0f); if (isIntegerScale) { float widthScale = (imageSizeX / pixelScale); float heightScale = (imageSizeY / pixelScale); bool isImageCompatible = (Mathf.Approximately((widthScale - Mathf.Floor(widthScale)), 0.0f) && Mathf.Approximately((heightScale - Mathf.Floor(heightScale)), 0.0f)); resolvedUpscaler = isImageCompatible ? k_UpscalerName_Point : k_UpscalerName_Linear; } else { resolvedUpscaler = k_UpscalerName_Linear; } } return resolvedUpscaler; } #else /// /// Returns the best supported image upscaling filter based on the provided upscaling filter selection /// /// Size of the final image /// Scale being applied to the final image size /// Upscaling filter selected by the user /// Either the original filter provided, or the best replacement available static ImageUpscalingFilter ResolveUpscalingFilterSelection(Vector2 imageSize, float renderScale, UpscalingFilterSelection selection) { // By default we just use linear filtering since it's the most compatible choice ImageUpscalingFilter filter = ImageUpscalingFilter.Linear; // Fall back to the automatic filter if the selected filter isn't supported on the current platform or rendering environment if ((selection == UpscalingFilterSelection.FSR && !FSRUtils.IsSupported()) || (selection == UpscalingFilterSelection.STP && !STP.IsSupported()) ) { selection = UpscalingFilterSelection.Auto; } switch (selection) { case UpscalingFilterSelection.Auto: { // The user selected "auto" for their upscaling filter so we should attempt to choose the best filter // for the current situation. When the current resolution and render scale are compatible with integer // scaling we use the point sampling filter. Otherwise we just use the default filter (linear). float pixelScale = (1.0f / renderScale); bool isIntegerScale = Mathf.Approximately((pixelScale - Mathf.Floor(pixelScale)), 0.0f); if (isIntegerScale) { float widthScale = (imageSize.x / pixelScale); float heightScale = (imageSize.y / pixelScale); bool isImageCompatible = (Mathf.Approximately((widthScale - Mathf.Floor(widthScale)), 0.0f) && Mathf.Approximately((heightScale - Mathf.Floor(heightScale)), 0.0f)); if (isImageCompatible) { filter = ImageUpscalingFilter.Point; } } break; } case UpscalingFilterSelection.Linear: { // Do nothing since linear is already the default break; } case UpscalingFilterSelection.Point: { filter = ImageUpscalingFilter.Point; break; } case UpscalingFilterSelection.FSR: { filter = ImageUpscalingFilter.FSR; break; } case UpscalingFilterSelection.STP: { filter = ImageUpscalingFilter.STP; break; } } return filter; } #endif /// /// Checks if the hardware (main display and platform) and the render pipeline support HDR. /// /// True if the main display and platform support HDR and HDR output is enabled on the platform. internal static bool HDROutputForMainDisplayIsActive() { bool hdrOutputSupported = SystemInfo.hdrDisplaySupportFlags.HasFlag(HDRDisplaySupportFlags.Supported) && asset.supportsHDR; bool hdrOutputActive = HDROutputSettings.main.available && HDROutputSettings.main.active; return hdrOutputSupported && hdrOutputActive; } /// /// Checks if any of the display devices we can output to are HDR capable and enabled. /// /// Return true if any of the display devices we can output HDR to have enabled HDR output internal static bool HDROutputForAnyDisplayIsActive() { bool hdrDisplayOutputActive = HDROutputForMainDisplayIsActive(); #if ENABLE_VR && ENABLE_XR_MODULE // If we are rendering to xr then we need to look at the XR Display rather than the main non-xr display. if (XRSystem.displayActive) { hdrDisplayOutputActive |= XRSystem.isHDRDisplayOutputActive; } #endif return hdrDisplayOutputActive; } // We only want to enable HDR Output for the game view once // since the game itself might want to control this internal bool enableHDROutputOnce = true; // We only want to warn once when the render pipeline asset HDR rendering support changes // and HDR output is active, which is incompatible at the render pipeline asset level. internal bool warnedRuntimeSwitchHDROutputToSDROutput = false; /// /// Configures the render pipeline to render to HDR output or disables HDR output. /// #if UNITY_2021_1_OR_NEWER void SetHDRState(List cameras) #else void SetHDRState(Camera[] cameras) #endif { bool hdrOutputActive = HDROutputSettings.main.available && HDROutputSettings.main.active; bool hdrOutputIncompatibleWithSDRRendering = hdrOutputActive && HDROutputSettings.main.displayColorGamut != ColorGamut.Rec709; // If the pipeline doesn't support HDR rendering, output to SDR. bool supportsSwitchingHDROutput = SystemInfo.hdrDisplaySupportFlags.HasFlag(HDRDisplaySupportFlags.RuntimeSwitchable); bool switchHDROutputToSDROutput = !asset.supportsHDR && hdrOutputActive && hdrOutputIncompatibleWithSDRRendering; if (switchHDROutputToSDROutput && !warnedRuntimeSwitchHDROutputToSDROutput) { if (supportsSwitchingHDROutput) { Debug.Log("HDR output is being disabled because the current Render Pipeline Asset does not support HDR rendering."); HDROutputSettings.main.RequestHDRModeChange(false); } else { Debug.LogWarning("HDR output is active and cannot be switched off at runtime, but the current Render Pipeline Asset does not support HDR rendering. Image may appear underexposed or oversaturated."); } warnedRuntimeSwitchHDROutputToSDROutput = true; } // Reset the warning flag as soon as the RP asset supports HDR rendering if (warnedRuntimeSwitchHDROutputToSDROutput && asset.supportsHDR) warnedRuntimeSwitchHDROutputToSDROutput = false; #if UNITY_EDITOR bool requestedHDRModeChange = false; // Automatically switch to HDR in the editor if it's available if (supportsSwitchingHDROutput && asset.supportsHDR && PlayerSettings.useHDRDisplay && HDROutputSettings.main.available) { #if UNITY_2021_1_OR_NEWER int cameraCount = cameras.Count; #else int cameraCount = cameras.Length; #endif if (cameraCount > 0 && cameras[0].cameraType != CameraType.Game) { requestedHDRModeChange = hdrOutputActive; HDROutputSettings.main.RequestHDRModeChange(false); } else if (enableHDROutputOnce) { requestedHDRModeChange = !hdrOutputActive; HDROutputSettings.main.RequestHDRModeChange(true); enableHDROutputOnce = false; } } if (requestedHDRModeChange || switchHDROutputToSDROutput) { // Repaint scene views and game views so the HDR mode request is applied UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); } #endif // Make sure HDR auto tonemap is off if the URP is handling it if (hdrOutputActive) { HDROutputSettings.main.automaticHDRTonemapping = false; } } internal static void GetHDROutputLuminanceParameters(HDROutputUtils.HDRDisplayInformation hdrDisplayInformation, ColorGamut hdrDisplayColorGamut, Tonemapping tonemapping, out Vector4 hdrOutputParameters) { float minNits = hdrDisplayInformation.minToneMapLuminance; float maxNits = hdrDisplayInformation.maxToneMapLuminance; float paperWhite = hdrDisplayInformation.paperWhiteNits; if (!tonemapping.detectPaperWhite.value) { paperWhite = tonemapping.paperWhite.value; } if (!tonemapping.detectBrightnessLimits.value) { minNits = tonemapping.minNits.value; maxNits = tonemapping.maxNits.value; } hdrOutputParameters = new Vector4(minNits, maxNits, paperWhite, 1f / paperWhite); } internal static void GetHDROutputGradingParameters(Tonemapping tonemapping, out Vector4 hdrOutputParameters) { int eetfMode = 0; float hueShift = 0.0f; switch (tonemapping.mode.value) { case TonemappingMode.Neutral: eetfMode = (int)tonemapping.neutralHDRRangeReductionMode.value; hueShift = tonemapping.hueShiftAmount.value; break; case TonemappingMode.ACES: eetfMode = (int)tonemapping.acesPreset.value; break; } hdrOutputParameters = new Vector4(eetfMode, hueShift, 0.0f, 0.0f); } #if ENABLE_ADAPTIVE_PERFORMANCE static void ApplyAdaptivePerformance(UniversalCameraData cameraData) { var noFrontToBackOpaqueFlags = SortingCriteria.SortingLayer | SortingCriteria.RenderQueue | SortingCriteria.OptimizeStateChanges | SortingCriteria.CanvasOrder; if (AdaptivePerformance.AdaptivePerformanceRenderSettings.SkipFrontToBackSorting) cameraData.defaultOpaqueSortFlags = noFrontToBackOpaqueFlags; var MaxShadowDistanceMultiplier = AdaptivePerformance.AdaptivePerformanceRenderSettings.MaxShadowDistanceMultiplier; cameraData.maxShadowDistance *= MaxShadowDistanceMultiplier; var RenderScaleMultiplier = AdaptivePerformance.AdaptivePerformanceRenderSettings.RenderScaleMultiplier; cameraData.renderScale *= RenderScaleMultiplier; // TODO if (!cameraData.xr.enabled) { cameraData.cameraTargetDescriptor.width = Mathf.Max(1, (int)(cameraData.pixelWidth * cameraData.renderScale)); cameraData.cameraTargetDescriptor.height = Mathf.Max(1, (int)(cameraData.pixelHeight * cameraData.renderScale)); #if ENABLE_UPSCALER_FRAMEWORK IUpscaler activeUpscaler = upscaling.activeUpscaler; if (TryGetUpscalerDictatedResolution(cameraData, activeUpscaler, out var upscalerRenderSize)) { cameraData.cameraTargetDescriptor.width = Mathf.Max(1, upscalerRenderSize.x); cameraData.cameraTargetDescriptor.height = Mathf.Max(1, upscalerRenderSize.y); } else { // Upscaler doesn't dictate the resolution but may constrain it to a supported range; clamp into it. var clamped = ClampRenderSizeToUpscalerBounds(cameraData, activeUpscaler, new Vector2Int(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height)); cameraData.cameraTargetDescriptor.width = Mathf.Max(1, clamped.x); cameraData.cameraTargetDescriptor.height = Mathf.Max(1, clamped.y); } #endif cameraData.scaledWidth = cameraData.cameraTargetDescriptor.width; cameraData.scaledHeight = cameraData.cameraTargetDescriptor.height; } var antialiasingQualityIndex = (int)cameraData.antialiasingQuality - AdaptivePerformance.AdaptivePerformanceRenderSettings.AntiAliasingQualityBias; if (antialiasingQualityIndex < 0) cameraData.antialiasing = AntialiasingMode.None; cameraData.antialiasingQuality = (AntialiasingQuality)Mathf.Clamp(antialiasingQualityIndex, (int)AntialiasingQuality.Low, (int)AntialiasingQuality.High); } static void ApplyAdaptivePerformance(ContextContainer frameData) { UniversalRenderingData renderingData = frameData.Get(); UniversalShadowData shadowData = frameData.Get(); UniversalPostProcessingData postProcessingData = frameData.Get(); var MainLightShadowmapResolutionMultiplier = AdaptivePerformance.AdaptivePerformanceRenderSettings.MainLightShadowmapResolutionMultiplier; shadowData.mainLightShadowmapWidth = (int)(shadowData.mainLightShadowmapWidth * MainLightShadowmapResolutionMultiplier); shadowData.mainLightShadowmapHeight = (int)(shadowData.mainLightShadowmapHeight * MainLightShadowmapResolutionMultiplier); var MainLightShadowCascadesCountBias = AdaptivePerformance.AdaptivePerformanceRenderSettings.MainLightShadowCascadesCountBias; shadowData.mainLightShadowCascadesCount = Mathf.Clamp(shadowData.mainLightShadowCascadesCount - MainLightShadowCascadesCountBias, 1, 4); var shadowQualityIndex = AdaptivePerformance.AdaptivePerformanceRenderSettings.ShadowQualityBias; for (int i = 0; i < shadowQualityIndex; i++) { if (shadowData.supportsSoftShadows) { shadowData.supportsSoftShadows = false; continue; } if (shadowData.supportsAdditionalLightShadows) { shadowData.supportsAdditionalLightShadows = false; continue; } if (shadowData.supportsMainLightShadows) { shadowData.supportsMainLightShadows = false; continue; } break; } if (AdaptivePerformance.AdaptivePerformanceRenderSettings.LutBias >= 1 && postProcessingData.lutSize == 32) postProcessingData.lutSize = 16; } #endif /// /// Data structure describing the data for a specific render request /// public class SingleCameraRequest { /// /// Target texture /// public RenderTexture destination = null; /// /// Target texture mip level /// public int mipLevel = 0; /// /// Target texture cubemap face /// public CubemapFace face = CubemapFace.Unknown; /// /// Target texture slice /// public int slice = 0; } static AdditionalLightsShadowAtlasLayout BuildAdditionalLightsShadowAtlasLayout(UniversalLightData lightData, UniversalShadowData shadowData, UniversalCameraData cameraData) { using var profScope = new ProfilingScope(Profiling.Pipeline.buildAdditionalLightsShadowAtlasLayout); return new AdditionalLightsShadowAtlasLayout(lightData, shadowData, cameraData); } /// /// Enforce under specific circumstances whether URP or native engine triggers the UI Overlay rendering /// static void AdjustUIOverlayOwnership(int cameraCount) { // If rendering to XR device, we don't render Screen Space UI overlay within SRP as the overlay should not be visible in HMD eyes, only when mirroring (after SRP XR Mirror pass) // If there is no camera to render in URP, SS UI overlay also has to be rendered in the engine if (XRSystem.displayActive || cameraCount == 0) { SupportedRenderingFeatures.active.rendersUIOverlay = false; } else { // Otherwise we enforce SS UI overlay rendering in URP // If needed, users can still request its rendering to be after URP // by setting rendersUIOverlay (public API) to false in a callback added to RenderPipelineManager.beginContextRendering SupportedRenderingFeatures.active.rendersUIOverlay = true; } } #if UNITY_EDITOR protected override bool IsPreviewSupported(Camera camera, out string reason) { if (camera != null && camera.TryGetComponent(out var additionalData) && additionalData.renderType == CameraRenderType.Overlay) { reason = "Overlay camera cannot be previewed directly.\nYou need to use a base camera instead."; return false; } return base.IsPreviewSupported(camera, out reason); } #endif } }