using System; using System.Diagnostics; using System.Collections.Generic; using Unity.Collections; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.Rendering.Universal { /// /// Class ScriptableRenderer implements a rendering strategy. It describes how culling and lighting works and /// the effects supported. /// /// A renderer can be used for all cameras or be overridden on a per-camera basis. It will implement light culling and setup /// and describe a list of ScriptableRenderPass to execute in a frame. The renderer can be extended to support more effect with additional /// ScriptableRendererFeature. Resources for the renderer are serialized in ScriptableRendererData. /// /// he renderer resources are serialized in ScriptableRendererData. /// /// /// /// [MovedFrom("UnityEngine.Rendering.LWRP")] public abstract class ScriptableRenderer : IDisposable { /// /// Configures the supported features for this renderer. When creating custom renderers /// for Universal Render Pipeline you can choose to opt-in or out for specific features. /// public class RenderingFeatures { /// /// This setting controls if the camera editor should display the camera stack category. /// Renderers that don't support camera stacking will only render camera of type CameraRenderType.Base /// /// /// public bool cameraStacking { get; set; } = false; } void SetShaderTimeValues(float time, float deltaTime, float smoothDeltaTime, CommandBuffer cmd = null) { // We make these parameters to mirror those described in `https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html float timeEights = time / 8f; float timeFourth = time / 4f; float timeHalf = time / 2f; // Time values Vector4 timeVector = time * new Vector4(1f / 20f, 1f, 2f, 3f); Vector4 sinTimeVector = new Vector4(Mathf.Sin(timeEights), Mathf.Sin(timeFourth), Mathf.Sin(timeHalf), Mathf.Sin(time)); Vector4 cosTimeVector = new Vector4(Mathf.Cos(timeEights), Mathf.Cos(timeFourth), Mathf.Cos(timeHalf), Mathf.Cos(time)); Vector4 deltaTimeVector = new Vector4(deltaTime, 1f / deltaTime, smoothDeltaTime, 1f / smoothDeltaTime); Vector4 timeParametersVector = new Vector4(time, Mathf.Sin(time), Mathf.Cos(time), 0.0f); if (cmd == null) { Shader.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._Time, timeVector); Shader.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._SinTime, sinTimeVector); Shader.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._CosTime, cosTimeVector); Shader.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer.unity_DeltaTime, deltaTimeVector); Shader.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._TimeParameters, timeParametersVector); } else { cmd.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._Time, timeVector); cmd.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._SinTime, sinTimeVector); cmd.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._CosTime, cosTimeVector); cmd.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer.unity_DeltaTime, deltaTimeVector); cmd.SetGlobalVector(UniversalRenderPipeline.PerFrameBuffer._TimeParameters, timeParametersVector); } } public RenderTargetIdentifier cameraColorTarget { get => m_CameraColorTarget; } public RenderTargetIdentifier cameraDepth { get => m_CameraDepthTarget; } protected List rendererFeatures { get => m_RendererFeatures; } protected List activeRenderPassQueue { get => m_ActiveRenderPassQueue; } /// /// Supported rendering features by this renderer. /// /// public RenderingFeatures supportedRenderingFeatures { get; set; } = new RenderingFeatures(); static class RenderPassBlock { // Executes render passes that are inputs to the main rendering // but don't depend on camera state. They all render in monoscopic mode. f.ex, shadow maps. public static readonly int BeforeRendering = 0; // Main bulk of render pass execution. They required camera state to be properly set // and when enabled they will render in stereo. public static readonly int MainRenderingOpaque = 1; public static readonly int MainRenderingTransparent = 2; // Execute after Post-processing. public static readonly int AfterRendering = 3; } const int k_RenderPassBlockCount = 4; List m_ActiveRenderPassQueue = new List(32); List m_RendererFeatures = new List(10); RenderTargetIdentifier m_CameraColorTarget; RenderTargetIdentifier m_CameraDepthTarget; bool m_FirstTimeCameraColorTargetIsBound = true; // flag used to track when m_CameraColorTarget should be cleared (if necessary), as well as other special actions only performed the first time m_CameraColorTarget is bound as a render target bool m_FirstTimeCameraDepthTargetIsBound = true; // flag used to track when m_CameraDepthTarget should be cleared (if necessary), the first time m_CameraDepthTarget is bound as a render target bool m_XRRenderTargetNeedsClear = false; const string k_SetCameraRenderStateTag = "Clear Render State"; const string k_SetRenderTarget = "Set RenderTarget"; const string k_ReleaseResourcesTag = "Release Resources"; static RenderTargetIdentifier[] m_ActiveColorAttachments = new RenderTargetIdentifier[]{0, 0, 0, 0, 0, 0, 0, 0 }; static RenderTargetIdentifier m_ActiveDepthAttachment; static bool m_InsideStereoRenderBlock; // CommandBuffer.SetRenderTarget(RenderTargetIdentifier[] colors, RenderTargetIdentifier depth, int mipLevel, CubemapFace cubemapFace, int depthSlice); // called from CoreUtils.SetRenderTarget will issue a warning assert from native c++ side if "colors" array contains some invalid RTIDs. // To avoid that warning assert we trim the RenderTargetIdentifier[] arrays we pass to CoreUtils.SetRenderTarget. // To avoid re-allocating a new array every time we do that, we re-use one of these arrays: static RenderTargetIdentifier[][] m_TrimmedColorAttachmentCopies = new RenderTargetIdentifier[][] { new RenderTargetIdentifier[0], // m_TrimmedColorAttachmentCopies[0] is an array of 0 RenderTargetIdentifier - only used to make indexing code easier to read new RenderTargetIdentifier[]{0}, // m_TrimmedColorAttachmentCopies[1] is an array of 1 RenderTargetIdentifier new RenderTargetIdentifier[]{0, 0}, // m_TrimmedColorAttachmentCopies[2] is an array of 2 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0}, // m_TrimmedColorAttachmentCopies[3] is an array of 3 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0, 0}, // m_TrimmedColorAttachmentCopies[4] is an array of 4 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0, 0, 0}, // m_TrimmedColorAttachmentCopies[5] is an array of 5 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0, 0, 0, 0}, // m_TrimmedColorAttachmentCopies[6] is an array of 6 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0, 0, 0, 0, 0}, // m_TrimmedColorAttachmentCopies[7] is an array of 7 RenderTargetIdentifiers new RenderTargetIdentifier[]{0, 0, 0, 0, 0, 0, 0, 0 }, // m_TrimmedColorAttachmentCopies[8] is an array of 8 RenderTargetIdentifiers }; internal static void ConfigureActiveTarget(RenderTargetIdentifier colorAttachment, RenderTargetIdentifier depthAttachment) { m_ActiveColorAttachments[0] = colorAttachment; for (int i = 1; i < m_ActiveColorAttachments.Length; ++i) m_ActiveColorAttachments[i] = 0; m_ActiveDepthAttachment = depthAttachment; } public ScriptableRenderer(ScriptableRendererData data) { foreach (var feature in data.rendererFeatures) { if (feature == null) continue; feature.Create(); m_RendererFeatures.Add(feature); } Clear(CameraRenderType.Base); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } /// /// Configures the camera target. /// /// Camera color target. Pass BuiltinRenderTextureType.CameraTarget if rendering to backbuffer. /// Camera depth target. Pass BuiltinRenderTextureType.CameraTarget if color has depth or rendering to backbuffer. public void ConfigureCameraTarget(RenderTargetIdentifier colorTarget, RenderTargetIdentifier depthTarget) { m_CameraColorTarget = colorTarget; m_CameraDepthTarget = depthTarget; } /// /// Configures the render passes that will execute for this renderer. /// This method is called per-camera every frame. /// /// Use this render context to issue any draw commands during execution. /// Current render state information. /// /// public abstract void Setup(ScriptableRenderContext context, ref RenderingData renderingData); /// /// Override this method to implement the lighting setup for the renderer. You can use this to /// compute and upload light CBUFFER for example. /// /// Use this render context to issue any draw commands during execution. /// Current render state information. public virtual void SetupLights(ScriptableRenderContext context, ref RenderingData renderingData) { } /// /// Override this method to configure the culling parameters for the renderer. You can use this to configure if /// lights should be culled per-object or the maximum shadow distance for example. /// /// Use this to change culling parameters used by the render pipeline. /// Current render state information. public virtual void SetupCullingParameters(ref ScriptableCullingParameters cullingParameters, ref CameraData cameraData) { } /// /// Called upon finishing rendering the camera stack. You can release any resources created by the renderer here. /// /// public virtual void FinishRendering(CommandBuffer cmd) { } /// /// Execute the enqueued render passes. This automatically handles editor and stereo rendering. /// /// Use this render context to issue any draw commands during execution. /// Current render state information. public void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { ref CameraData cameraData = ref renderingData.cameraData; Camera camera = cameraData.camera; CommandBuffer cmd = CommandBufferPool.Get(k_SetCameraRenderStateTag); // Initialize Camera Render State SetCameraRenderState(cmd, ref cameraData); context.ExecuteCommandBuffer(cmd); cmd.Clear(); // Sort the render pass queue SortStable(m_ActiveRenderPassQueue); // Cache the time for after the call to `SetupCameraProperties` and set the time variables in shader // For now we set the time variables per camera, as we plan to remove `SetupCamearProperties`. // Setting the time per frame would take API changes to pass the variable to each camera render. // Once `SetupCameraProperties` is gone, the variable should be set higher in the call-stack. #if UNITY_EDITOR float time = Application.isPlaying ? Time.time : Time.realtimeSinceStartup; #else float time = Time.time; #endif float deltaTime = Time.deltaTime; float smoothDeltaTime = Time.smoothDeltaTime; SetShaderTimeValues(time, deltaTime, smoothDeltaTime); // Upper limits for each block. Each block will contains render passes with events below the limit. NativeArray blockEventLimits = new NativeArray(k_RenderPassBlockCount, Allocator.Temp); blockEventLimits[RenderPassBlock.BeforeRendering] = RenderPassEvent.BeforeRenderingPrepasses; blockEventLimits[RenderPassBlock.MainRenderingOpaque] = RenderPassEvent.AfterRenderingOpaques; blockEventLimits[RenderPassBlock.MainRenderingTransparent] = RenderPassEvent.AfterRenderingPostProcessing; blockEventLimits[RenderPassBlock.AfterRendering] = (RenderPassEvent)Int32.MaxValue; NativeArray blockRanges = new NativeArray(blockEventLimits.Length + 1, Allocator.Temp); // blockRanges[0] is always 0 // blockRanges[i] is the index of the first RenderPass found in m_ActiveRenderPassQueue that has a ScriptableRenderPass.renderPassEvent higher than blockEventLimits[i] (i.e, should be executed after blockEventLimits[i]) // blockRanges[blockEventLimits.Length] is m_ActiveRenderPassQueue.Count FillBlockRanges(blockEventLimits, blockRanges); blockEventLimits.Dispose(); SetupLights(context, ref renderingData); // Before Render Block. This render blocks always execute in mono rendering. // Camera is not setup. Lights are not setup. // Used to render input textures like shadowmaps. ExecuteBlock(RenderPassBlock.BeforeRendering, blockRanges, context, ref renderingData); for (int eyeIndex = 0; eyeIndex < renderingData.cameraData.numberOfXRPasses; ++eyeIndex) { /// Configure shader variables and other unity properties that are required for rendering. /// * Setup Camera RenderTarget and Viewport /// * VR Camera Setup and SINGLE_PASS_STEREO props /// * Setup camera view, projection and their inverse matrices. /// * Setup properties: _WorldSpaceCameraPos, _ProjectionParams, _ScreenParams, _ZBufferParams, unity_OrthoParams /// * Setup camera world clip planes properties /// * Setup HDR keyword /// * Setup global time properties (_Time, _SinTime, _CosTime) bool stereoEnabled = renderingData.cameraData.isStereoEnabled; context.SetupCameraProperties(camera, stereoEnabled, eyeIndex); // If overlay camera, we have to reset projection related matrices due to inheriting viewport from base // camera. This changes the aspect ratio, which requires to recompute projection. // TODO: We need to expose all work done in SetupCameraProperties above to c# land. This not only // avoids resetting values but also guarantee values are correct for all systems. // Known Issue: billboard will not work with camera stacking when using viewport with aspect ratio different from default aspect. if (cameraData.renderType == CameraRenderType.Overlay) { cmd.SetViewProjectionMatrices(cameraData.viewMatrix, cameraData.projectionMatrix); } // Override time values from when `SetupCameraProperties` were called. // They might be a frame behind. // We can remove this after removing `SetupCameraProperties` as the values should be per frame, and not per camera. SetShaderTimeValues(time, deltaTime, smoothDeltaTime, cmd); context.ExecuteCommandBuffer(cmd); cmd.Clear(); if (stereoEnabled) BeginXRRendering(context, camera, eyeIndex); #if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER var localCmd = CommandBufferPool.Get(string.Empty); //Triggers dispatch per camera, all global parameters should have been setup at this stage. VFX.VFXManager.ProcessCameraCommand(camera, localCmd); context.ExecuteCommandBuffer(localCmd); CommandBufferPool.Release(localCmd); #endif // In the opaque and transparent blocks the main rendering executes. // Opaque blocks... ExecuteBlock(RenderPassBlock.MainRenderingOpaque, blockRanges, context, ref renderingData, eyeIndex); // Transparent blocks... ExecuteBlock(RenderPassBlock.MainRenderingTransparent, blockRanges, context, ref renderingData, eyeIndex); // Draw Gizmos... DrawGizmos(context, camera, GizmoSubset.PreImageEffects); // In this block after rendering drawing happens, e.g, post processing, video player capture. ExecuteBlock(RenderPassBlock.AfterRendering, blockRanges, context, ref renderingData, eyeIndex); if (stereoEnabled) EndXRRendering(context, renderingData, eyeIndex); } DrawGizmos(context, camera, GizmoSubset.PostImageEffects); InternalFinishRendering(context, renderingData.resolveFinalTarget); blockRanges.Dispose(); CommandBufferPool.Release(cmd); } /// /// Enqueues a render pass for execution. /// /// Render pass to be enqueued. public void EnqueuePass(ScriptableRenderPass pass) { m_ActiveRenderPassQueue.Add(pass); } #region deprecated [Obsolete("Use GetCameraClearFlag(ref CameraData cameraData) instead")] protected static ClearFlag GetCameraClearFlag(CameraClearFlags cameraClearFlags) { #if UNITY_EDITOR // We need public API to tell if FrameDebugger is active and enabled. In that case // we want to force a clear to see properly the drawcall stepping. // For now, to fix FrameDebugger in Editor, we force a clear. cameraClearFlags = CameraClearFlags.SolidColor; #endif // Always clear on first render pass in mobile as it's same perf of DontCare and avoid tile clearing issues. if (Application.isMobilePlatform) return ClearFlag.All; if ((cameraClearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null) || cameraClearFlags == CameraClearFlags.Nothing) return ClearFlag.Depth; return ClearFlag.All; } #endregion /// /// Returns a clear flag based on CameraClearFlags. /// /// Camera clear flags. /// A clear flag that tells if color and/or depth should be cleared. protected static ClearFlag GetCameraClearFlag(ref CameraData cameraData) { var cameraClearFlags = cameraData.camera.clearFlags; #if UNITY_EDITOR // We need public API to tell if FrameDebugger is active and enabled. In that case // we want to force a clear to see properly the drawcall stepping. // For now, to fix FrameDebugger in Editor, we force a clear. cameraClearFlags = CameraClearFlags.SolidColor; #endif // Universal RP doesn't support CameraClearFlags.DepthOnly and CameraClearFlags.Nothing. // CameraClearFlags.DepthOnly has the same effect of CameraClearFlags.SolidColor // CameraClearFlags.Nothing clears Depth on PC/Desktop and in mobile it clears both // depth and color. // CameraClearFlags.Skybox clears depth only. // Implementation details: // Camera clear flags are used to initialize the attachments on the first render pass. // ClearFlag is used together with Tile Load action to figure out how to clear the camera render target. // In Tile Based GPUs ClearFlag.Depth + RenderBufferLoadAction.DontCare becomes DontCare load action. // While ClearFlag.All + RenderBufferLoadAction.DontCare become Clear load action. // In mobile we force ClearFlag.All as DontCare doesn't have noticeable perf. difference from Clear // and this avoid tile clearing issue when not rendering all pixels in some GPUs. // In desktop/consoles there's actually performance difference between DontCare and Clear. // RenderBufferLoadAction.DontCare in PC/Desktop behaves as not clearing screen // RenderBufferLoadAction.DontCare in Vulkan/Metal behaves as DontCare load action // RenderBufferLoadAction.DontCare in GLES behaves as glInvalidateBuffer // Overlay cameras composite on top of previous ones. They don't clear color. // For overlay cameras we check if depth should be cleared on not. if (cameraData.renderType == CameraRenderType.Overlay) return (cameraData.clearDepth) ? ClearFlag.Depth : ClearFlag.None; // Always clear on first render pass in mobile as it's same perf of DontCare and avoid tile clearing issues. if (Application.isMobilePlatform) return ClearFlag.All; if ((cameraClearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null) || cameraClearFlags == CameraClearFlags.Nothing) return ClearFlag.Depth; return ClearFlag.All; } // Initialize Camera Render State // Place all per-camera rendering logic that is generic for all types of renderers here. void SetCameraRenderState(CommandBuffer cmd, ref CameraData cameraData) { // Reset per-camera shader keywords. They are enabled depending on which render passes are executed. cmd.DisableShaderKeyword(ShaderKeywordStrings.MainLightShadows); cmd.DisableShaderKeyword(ShaderKeywordStrings.MainLightShadowCascades); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightsVertex); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightsPixel); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightShadows); cmd.DisableShaderKeyword(ShaderKeywordStrings.SoftShadows); cmd.DisableShaderKeyword(ShaderKeywordStrings.MixedLightingSubtractive); cmd.DisableShaderKeyword(ShaderKeywordStrings.LinearToSRGBConversion); } internal void Clear(CameraRenderType cameraType) { m_ActiveColorAttachments[0] = BuiltinRenderTextureType.CameraTarget; for (int i = 1; i < m_ActiveColorAttachments.Length; ++i) m_ActiveColorAttachments[i] = 0; m_ActiveDepthAttachment = BuiltinRenderTextureType.CameraTarget; m_InsideStereoRenderBlock = false; m_FirstTimeCameraColorTargetIsBound = cameraType == CameraRenderType.Base; m_FirstTimeCameraDepthTargetIsBound = true; m_ActiveRenderPassQueue.Clear(); m_CameraColorTarget = BuiltinRenderTextureType.CameraTarget; m_CameraDepthTarget = BuiltinRenderTextureType.CameraTarget; } void ExecuteBlock(int blockIndex, NativeArray blockRanges, ScriptableRenderContext context, ref RenderingData renderingData, int eyeIndex = 0, bool submit = false) { int endIndex = blockRanges[blockIndex + 1]; for (int currIndex = blockRanges[blockIndex]; currIndex < endIndex; ++currIndex) { var renderPass = m_ActiveRenderPassQueue[currIndex]; ExecuteRenderPass(context, renderPass, ref renderingData, eyeIndex); } if (submit) context.Submit(); } void ExecuteRenderPass(ScriptableRenderContext context, ScriptableRenderPass renderPass, ref RenderingData renderingData, int eyeIndex) { ref CameraData cameraData = ref renderingData.cameraData; Camera camera = cameraData.camera; bool firstTimeStereo = false; CommandBuffer cmd = CommandBufferPool.Get(k_SetRenderTarget); renderPass.Configure(cmd, cameraData.cameraTargetDescriptor); renderPass.eyeIndex = eyeIndex; ClearFlag cameraClearFlag = GetCameraClearFlag(ref cameraData); // We use a different code path for MRT since it calls a different version of API SetRenderTarget if (RenderingUtils.IsMRT(renderPass.colorAttachments)) { // In the MRT path we assume that all color attachments are REAL color attachments, // and that the depth attachment is a REAL depth attachment too. // Determine what attachments need to be cleared. ---------------- bool needCustomCameraColorClear = false; bool needCustomCameraDepthClear = false; int cameraColorTargetIndex = RenderingUtils.IndexOf(renderPass.colorAttachments, m_CameraColorTarget); if (cameraColorTargetIndex != -1 && (m_FirstTimeCameraColorTargetIsBound || (cameraData.isXRMultipass && m_XRRenderTargetNeedsClear) )) { m_FirstTimeCameraColorTargetIsBound = false; // register that we did clear the camera target the first time it was bound firstTimeStereo = true; // Overlay cameras composite on top of previous ones. They don't clear. // MTT: Commented due to not implemented yet // if (renderingData.cameraData.renderType == CameraRenderType.Overlay) // clearFlag = ClearFlag.None; // We need to specifically clear the camera color target. // But there is still a chance we don't need to issue individual clear() on each render-targets if they all have the same clear parameters. needCustomCameraColorClear = (cameraClearFlag & ClearFlag.Color) != (renderPass.clearFlag & ClearFlag.Color) || CoreUtils.ConvertSRGBToActiveColorSpace(camera.backgroundColor) != renderPass.clearColor; if(cameraData.isXRMultipass && m_XRRenderTargetNeedsClear) // For multipass mode, if m_XRRenderTargetNeedsClear == true, then both color and depth buffer need clearing (not just color) needCustomCameraDepthClear = (cameraClearFlag & ClearFlag.Depth) != (renderPass.clearFlag & ClearFlag.Depth); m_XRRenderTargetNeedsClear = false; // register that the XR camera multi-pass target does not need clear any more (until next call to BeginXRRendering) } // Note: if we have to give up the assumption that no depthTarget can be included in the MRT colorAttachments, we might need something like this: // int cameraTargetDepthIndex = IndexOf(renderPass.colorAttachments, m_CameraDepthTarget); // if( !renderTargetAlreadySet && cameraTargetDepthIndex != -1 && m_FirstTimeCameraDepthTargetIsBound) // { ... // } if (renderPass.depthAttachment == m_CameraDepthTarget && m_FirstTimeCameraDepthTargetIsBound) // note: should be split m_XRRenderTargetNeedsClear into m_XRColorTargetNeedsClear and m_XRDepthTargetNeedsClear and use m_XRDepthTargetNeedsClear here? { m_FirstTimeCameraDepthTargetIsBound = false; //firstTimeStereo = true; // <- we do not call this here as the first render pass might be a shadow pass (non-stereo) needCustomCameraDepthClear = (cameraClearFlag & ClearFlag.Depth) != (renderPass.clearFlag & ClearFlag.Depth); //m_XRRenderTargetNeedsClear = false; // note: is it possible that XR camera multi-pass target gets clear first when bound as depth target? // in this case we might need need to register that it does not need clear any more (until next call to BeginXRRendering) } // Perform all clear operations needed. ---------------- // We try to minimize calls to SetRenderTarget(). // We get here only if cameraColorTarget needs to be handled separately from the rest of the color attachments. if (needCustomCameraColorClear) { // Clear camera color render-target separately from the rest of the render-targets. if ((cameraClearFlag & ClearFlag.Color) != 0) SetRenderTarget(cmd, renderPass.colorAttachments[cameraColorTargetIndex], renderPass.depthAttachment, ClearFlag.Color, CoreUtils.ConvertSRGBToActiveColorSpace(camera.backgroundColor)); if ((renderPass.clearFlag & ClearFlag.Color) != 0) { uint otherTargetsCount = RenderingUtils.CountDistinct(renderPass.colorAttachments, m_CameraColorTarget); var nonCameraAttachments = m_TrimmedColorAttachmentCopies[otherTargetsCount]; int writeIndex = 0; for (int readIndex = 0; readIndex < renderPass.colorAttachments.Length; ++readIndex) { if (renderPass.colorAttachments[readIndex] != m_CameraColorTarget && renderPass.colorAttachments[readIndex] != 0) { nonCameraAttachments[writeIndex] = renderPass.colorAttachments[readIndex]; ++writeIndex; } } if (writeIndex != otherTargetsCount) Debug.LogError("writeIndex and otherTargetsCount values differed. writeIndex:" + writeIndex + " otherTargetsCount:" + otherTargetsCount); SetRenderTarget(cmd, nonCameraAttachments, m_CameraDepthTarget, ClearFlag.Color, renderPass.clearColor); } } // Bind all attachments, clear color only if there was no custom behaviour for cameraColorTarget, clear depth as needed. ClearFlag finalClearFlag = ClearFlag.None; finalClearFlag |= needCustomCameraDepthClear ? (cameraClearFlag & ClearFlag.Depth) : (renderPass.clearFlag & ClearFlag.Depth); finalClearFlag |= needCustomCameraColorClear ? 0 : (renderPass.clearFlag & ClearFlag.Color); // Only setup render target if current render pass attachments are different from the active ones. if (!RenderingUtils.SequenceEqual(renderPass.colorAttachments, m_ActiveColorAttachments) || renderPass.depthAttachment != m_ActiveDepthAttachment || finalClearFlag != ClearFlag.None) { int lastValidRTindex = RenderingUtils.LastValid(renderPass.colorAttachments); if (lastValidRTindex >= 0) { int rtCount = lastValidRTindex + 1; var trimmedAttachments = m_TrimmedColorAttachmentCopies[rtCount]; for (int i = 0; i < rtCount; ++i) trimmedAttachments[i] = renderPass.colorAttachments[i]; SetRenderTarget(cmd, trimmedAttachments, renderPass.depthAttachment, finalClearFlag, renderPass.clearColor); } } } else { // Currently in non-MRT case, color attachment can actually be a depth attachment. RenderTargetIdentifier passColorAttachment = renderPass.colorAttachment; RenderTargetIdentifier passDepthAttachment = renderPass.depthAttachment; // When render pass doesn't call ConfigureTarget we assume it's expected to render to camera target // which might be backbuffer or the framebuffer render textures. if (!renderPass.overrideCameraTarget) { passColorAttachment = m_CameraColorTarget; passDepthAttachment = m_CameraDepthTarget; } ClearFlag finalClearFlag = ClearFlag.None; Color finalClearColor; if (passColorAttachment == m_CameraColorTarget && (m_FirstTimeCameraColorTargetIsBound || (cameraData.isXRMultipass && m_XRRenderTargetNeedsClear))) { m_FirstTimeCameraColorTargetIsBound = false; // register that we did clear the camera target the first time it was bound finalClearFlag |= (cameraClearFlag & ClearFlag.Color); finalClearColor = CoreUtils.ConvertSRGBToActiveColorSpace(camera.backgroundColor); firstTimeStereo = true; if (m_FirstTimeCameraDepthTargetIsBound || (cameraData.isXRMultipass && m_XRRenderTargetNeedsClear)) { // m_CameraColorTarget can be an opaque pointer to a RenderTexture with depth-surface. // We cannot infer this information here, so we must assume both camera color and depth are first-time bound here (this is the legacy behaviour). m_FirstTimeCameraDepthTargetIsBound = false; finalClearFlag |= (cameraClearFlag & ClearFlag.Depth); } m_XRRenderTargetNeedsClear = false; // register that the XR camera multi-pass target does not need clear any more (until next call to BeginXRRendering) } else { finalClearFlag |= (renderPass.clearFlag & ClearFlag.Color); finalClearColor = renderPass.clearColor; } // Condition (m_CameraDepthTarget!=BuiltinRenderTextureType.CameraTarget) below prevents m_FirstTimeCameraDepthTargetIsBound flag from being reset during non-camera passes (such as Color Grading LUT). This ensures that in those cases, cameraDepth will actually be cleared during the later camera pass. if ( (m_CameraDepthTarget!=BuiltinRenderTextureType.CameraTarget ) && (passDepthAttachment == m_CameraDepthTarget || passColorAttachment == m_CameraDepthTarget) && m_FirstTimeCameraDepthTargetIsBound ) // note: should be split m_XRRenderTargetNeedsClear into m_XRColorTargetNeedsClear and m_XRDepthTargetNeedsClear and use m_XRDepthTargetNeedsClear here? { m_FirstTimeCameraDepthTargetIsBound = false; finalClearFlag |= (cameraClearFlag & ClearFlag.Depth); //firstTimeStereo = true; // <- we do not call this here as the first render pass might be a shadow pass (non-stereo) // finalClearFlag |= (cameraClearFlag & ClearFlag.Color); // <- m_CameraDepthTarget is never a color-surface, so no need to add this here. //m_XRRenderTargetNeedsClear = false; // note: is it possible that XR camera multi-pass target gets clear first when bound as depth target? // in this case we might need need to register that it does not need clear any more (until next call to BeginXRRendering) } else finalClearFlag |= (renderPass.clearFlag & ClearFlag.Depth); // Only setup render target if current render pass attachments are different from the active ones if (passColorAttachment != m_ActiveColorAttachments[0] || passDepthAttachment != m_ActiveDepthAttachment || finalClearFlag != ClearFlag.None) SetRenderTarget(cmd, passColorAttachment, passDepthAttachment, finalClearFlag, finalClearColor); } // We must execute the commands recorded at this point because potential call to context.StartMultiEye(cameraData.camera) below will alter internal renderer states // Also, we execute the commands recorded at this point to ensure SetRenderTarget is called before RenderPass.Execute context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); if (firstTimeStereo && cameraData.isStereoEnabled ) { // The following call alters internal renderer states (we can think of some of the states as global states). // So any cmd recorded before must be executed before calling into that built-in call. context.StartMultiEye(camera, eyeIndex); XRUtils.DrawOcclusionMesh(cmd, camera); } renderPass.Execute(context, ref renderingData); } void BeginXRRendering(ScriptableRenderContext context, Camera camera, int eyeIndex) { context.StartMultiEye(camera, eyeIndex); m_InsideStereoRenderBlock = true; m_XRRenderTargetNeedsClear = true; } void EndXRRendering(ScriptableRenderContext context, in RenderingData renderingData, int eyeIndex) { Camera camera = renderingData.cameraData.camera; context.StopMultiEye(camera); bool isLastPass = renderingData.resolveFinalTarget && (eyeIndex == renderingData.cameraData.numberOfXRPasses - 1); context.StereoEndRender(camera, eyeIndex, isLastPass); m_InsideStereoRenderBlock = false; } internal static void SetRenderTarget(CommandBuffer cmd, RenderTargetIdentifier colorAttachment, RenderTargetIdentifier depthAttachment, ClearFlag clearFlag, Color clearColor) { m_ActiveColorAttachments[0] = colorAttachment; for (int i = 1; i < m_ActiveColorAttachments.Length; ++i) m_ActiveColorAttachments[i] = 0; m_ActiveDepthAttachment = depthAttachment; RenderBufferLoadAction colorLoadAction = ((uint)clearFlag & (uint)ClearFlag.Color) != 0 ? RenderBufferLoadAction.DontCare : RenderBufferLoadAction.Load; RenderBufferLoadAction depthLoadAction = ((uint)clearFlag & (uint)ClearFlag.Depth) != 0 ? RenderBufferLoadAction.DontCare : RenderBufferLoadAction.Load; TextureDimension dimension = (m_InsideStereoRenderBlock) ? XRGraphics.eyeTextureDesc.dimension : TextureDimension.Tex2D; SetRenderTarget(cmd, colorAttachment, colorLoadAction, RenderBufferStoreAction.Store, depthAttachment, depthLoadAction, RenderBufferStoreAction.Store, clearFlag, clearColor, dimension); } static void SetRenderTarget( CommandBuffer cmd, RenderTargetIdentifier colorAttachment, RenderBufferLoadAction colorLoadAction, RenderBufferStoreAction colorStoreAction, ClearFlag clearFlags, Color clearColor, TextureDimension dimension) { if (dimension == TextureDimension.Tex2DArray) CoreUtils.SetRenderTarget(cmd, colorAttachment, clearFlags, clearColor, 0, CubemapFace.Unknown, -1); else CoreUtils.SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction, clearFlags, clearColor); } static void SetRenderTarget( CommandBuffer cmd, RenderTargetIdentifier colorAttachment, RenderBufferLoadAction colorLoadAction, RenderBufferStoreAction colorStoreAction, RenderTargetIdentifier depthAttachment, RenderBufferLoadAction depthLoadAction, RenderBufferStoreAction depthStoreAction, ClearFlag clearFlags, Color clearColor, TextureDimension dimension) { if (depthAttachment == BuiltinRenderTextureType.CameraTarget) { SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction, clearFlags, clearColor, dimension); } else { if (dimension == TextureDimension.Tex2DArray) CoreUtils.SetRenderTarget(cmd, colorAttachment, depthAttachment, clearFlags, clearColor, 0, CubemapFace.Unknown, -1); else CoreUtils.SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction, depthAttachment, depthLoadAction, depthStoreAction, clearFlags, clearColor); } } static void SetRenderTarget(CommandBuffer cmd, RenderTargetIdentifier[] colorAttachments, RenderTargetIdentifier depthAttachment, ClearFlag clearFlag, Color clearColor) { m_ActiveColorAttachments = colorAttachments; m_ActiveDepthAttachment = depthAttachment; CoreUtils.SetRenderTarget(cmd, colorAttachments, depthAttachment, clearFlag, clearColor); } [Conditional("UNITY_EDITOR")] void DrawGizmos(ScriptableRenderContext context, Camera camera, GizmoSubset gizmoSubset) { #if UNITY_EDITOR if (UnityEditor.Handles.ShouldRenderGizmos()) context.DrawGizmos(camera, gizmoSubset); #endif } // Fill in render pass indices for each block. End index is startIndex + 1. void FillBlockRanges(NativeArray blockEventLimits, NativeArray blockRanges) { int currRangeIndex = 0; int currRenderPass = 0; blockRanges[currRangeIndex++] = 0; // For each block, it finds the first render pass index that has an event // higher than the block limit. for (int i = 0; i < blockEventLimits.Length - 1; ++i) { while (currRenderPass < m_ActiveRenderPassQueue.Count && m_ActiveRenderPassQueue[currRenderPass].renderPassEvent < blockEventLimits[i]) currRenderPass++; blockRanges[currRangeIndex++] = currRenderPass; } blockRanges[currRangeIndex] = m_ActiveRenderPassQueue.Count; } void InternalFinishRendering(ScriptableRenderContext context, bool resolveFinalTarget) { CommandBuffer cmd = CommandBufferPool.Get(k_ReleaseResourcesTag); for (int i = 0; i < m_ActiveRenderPassQueue.Count; ++i) m_ActiveRenderPassQueue[i].FrameCleanup(cmd); // Happens when rendering the last camera in the camera stack. if (resolveFinalTarget) { for (int i = 0; i < m_ActiveRenderPassQueue.Count; ++i) m_ActiveRenderPassQueue[i].OnFinishCameraStackRendering(cmd); FinishRendering(cmd); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } internal static void SortStable(List list) { int j; for (int i = 1; i < list.Count; ++i) { ScriptableRenderPass curr = list[i]; j = i - 1; for (; j >= 0 && curr < list[j]; --j) list[j + 1] = list[j]; list[j + 1] = curr; } } } }