using System; using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal; // This renderer feature will replicate a "don't clear" behaviour by injecting two passes into the pipeline: // One pass that copies color at the end of a frame // Another pass that draws the content of the copied texture at the beginning of a new frame // In this version of the sample we provide implementations for both RenderGraph and non-RenderGraph pipelines. // This way you can easily see what changed and how to manage code bases with backwards compatibility public class KeepFrameFeature : ScriptableRendererFeature { // This pass is responsible for copying color to a specified destination class CopyFramePass : ScriptableRenderPass { RTHandle m_Destination; public void Setup(RTHandle destination) { m_Destination = destination; } #pragma warning disable 618, 672 // Type or member is obsolete, Member overrides obsolete member // Unity calls the Execute method in the Compatibility mode public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (renderingData.cameraData.camera.cameraType != CameraType.Game) return; var source = renderingData.cameraData.renderer.cameraColorTargetHandle; CommandBuffer cmd = CommandBufferPool.Get("CopyFramePass"); Blit(cmd, source, m_Destination); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } #pragma warning restore 618, 672 // RecordRenderGraph is called for the RenderGraph path. // Because RenderGraph has to calculate internally how resources are used we must be aware of 2 // distinct timelines inside this method: one for recording resource usage and one for recording draw commands. // It is important to scope resources correctly as global state may change between the execution times of each. public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourceData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); if (cameraData.camera.cameraType != CameraType.Game) return; TextureHandle source = resourceData.activeColorTexture; // When using the RenderGraph API the lifetime and ownership of resources is managed by the render graph system itself. // This allows for optimal resource usage and other optimizations to be done automatically for the user. // In the cases where resources must persist across frames, between different cameras or when users want // to manage their lifetimes themselves, the resources must be imported when recording the render pass. TextureHandle destination = renderGraph.ImportTexture(m_Destination); if (!source.IsValid() || !destination.IsValid()) return; RenderGraphUtils.BlitMaterialParameters para = new(source, destination, Blitter.GetBlitMaterial(TextureDimension.Tex2D), 0); renderGraph.AddBlitPass(para, "Copy Frame Pass"); } } // This pass is responsible for drawing the old color to a full screen quad class DrawOldFramePass : ScriptableRenderPass { class PassData { public TextureHandle source; public Material material; public string name; } Material m_DrawOldFrameMaterial; RTHandle m_Handle; string m_TextureName; public void Setup(Material drawOldFrameMaterial, RTHandle handle, string textureName) { m_DrawOldFrameMaterial = drawOldFrameMaterial; m_TextureName = textureName; m_Handle = handle; } // This is an example of how to share code between RenderGraph and older non-RenderGraph setups. // The common draw commands are extracted in a private static method that gets called from both // Execute and render graph builder's SetRenderFunc. static void ExecutePass(RasterCommandBuffer cmd, RTHandle source, Material material) { if (material == null) return; Vector2 viewportScale = source.useScaling ? new Vector2(source.rtHandleProperties.rtHandleScale.x, source.rtHandleProperties.rtHandleScale.y) : Vector2.one; Blitter.BlitTexture(cmd, source, viewportScale, material, 0); } #pragma warning disable 618, 672 // Type or member is obsolete, Member overrides obsolete member // Unity calls the Execute method in the Compatibility mode public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { CommandBuffer cmd = CommandBufferPool.Get(nameof(DrawOldFramePass)); cmd.SetGlobalTexture(m_TextureName, m_Handle); var source = renderingData.cameraData.renderer.cameraColorTargetHandle; ExecutePass(CommandBufferHelpers.GetRasterCommandBuffer(cmd), source, m_DrawOldFrameMaterial); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } #pragma warning restore 618, 672 public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourceData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); TextureHandle oldFrameTextureHandle = renderGraph.ImportTexture(m_Handle); using (var builder = renderGraph.AddRasterRenderPass("Draw Old Frame Pass", out var passData)) { TextureHandle destination = resourceData.activeColorTexture; if (!oldFrameTextureHandle.IsValid() || !destination.IsValid()) return; passData.material = m_DrawOldFrameMaterial; passData.source = oldFrameTextureHandle; passData.name = m_TextureName; builder.UseTexture(oldFrameTextureHandle, AccessFlags.Read); builder.SetRenderAttachment(destination, 0, AccessFlags.Write); // Normally global state modifications are not allowed when using RenderGraph and will result in errors. // In the exceptional cases where this is intentional we must let the RenderGraph API know by calling // AllowGlobalStateModification(true). Use this only where necessary as it will introduce a sync point // in the frame which may have a negative impact on performance. builder.AllowGlobalStateModification(true); builder.SetRenderFunc((PassData data, RasterGraphContext context) => { context.cmd.SetGlobalTexture(data.name, data.source); ExecutePass(context.cmd, data.source, data.material); }); } } } [Serializable] public class Settings { [Tooltip("The material that is used when the old frame is redrawn at the start of the new frame (before opaques).")] public Material displayMaterial; [Tooltip("The name of the texture used for referencing the copied frame. (Defaults to _FrameCopyTex if empty)")] public string textureName; } CopyFramePass m_CopyFrame; DrawOldFramePass m_DrawOldFrame; RTHandle m_OldFrameHandle; public Settings settings = new Settings(); // In this function the passes are created and their point of injection is set public override void Create() { m_CopyFrame = new CopyFramePass(); m_CopyFrame.renderPassEvent = RenderPassEvent.AfterRenderingTransparents; // Frame color is copied late in the frame m_DrawOldFrame = new DrawOldFramePass(); m_DrawOldFrame.renderPassEvent = RenderPassEvent.BeforeRenderingOpaques; // Old frame is drawn early in the frame } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { var descriptor = renderingData.cameraData.cameraTargetDescriptor; descriptor.msaaSamples = 1; descriptor.depthBufferBits = 0; descriptor.graphicsFormat = GraphicsFormat.R8G8B8A8_SRGB; var textureName = String.IsNullOrEmpty(settings.textureName) ? "_FrameCopyTex" : settings.textureName; RenderingUtils.ReAllocateHandleIfNeeded(ref m_OldFrameHandle, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: textureName); m_CopyFrame.Setup(m_OldFrameHandle); m_DrawOldFrame.Setup(settings.displayMaterial, m_OldFrameHandle, textureName); renderer.EnqueuePass(m_CopyFrame); renderer.EnqueuePass(m_DrawOldFrame); } public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData) { // This path is not taken when using render graph. // The code to reallocate m_OldFrameHandle has been moved to AddRenderPasses in order to avoid duplication. } protected override void Dispose(bool disposing) { m_OldFrameHandle?.Release(); } }