TLDR: Scriptable Render Pass in the new Render Graph causes memory leak, why?
Unity version 6000.2.7f2, URP version 17.2.0
I tried writing a simple gaussian blur as a render feature in Unity 6. To have an efficient shader, I use one vertical and one horizontal pass for the blur. So, the first pass should blit into a temporary texture, and the second pass blits from the temp texture back to the camera texture. The code that I wrote works fine, but I noticed that it is causing a memory leak.
I suspect it is caused by the creation of a temporary texture every frame, but according to the render graph API, the render graph handles all disposal of temp textures. Did I misunderstand how I should be using the render graph? What is the correct way to use temporary textures in the Render Graph?
The Scriptable Render Feature Code:
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
public class GaussianOutlineRenderPass : ScriptableRenderPass
{
const string m_PassName = "GaussianOutlineColorPass";
Material m_BlitMaterial;
public void Setup(Material mat)
{
m_BlitMaterial = mat;
requiresIntermediateTexture = true;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();
var cameraData = frameData.Get<UniversalCameraData>();
cameraData.requiresDepthTexture = true;
if (resourceData.isActiveTargetBackBuffer)
{
Debug.LogError($"Skipping render pass. GaussianOutlineRenderFeature requires an intermediate ColorTexture, we can't use the BackBuffer as a texture input.");
return;
}
var source = resourceData.activeColorTexture;
var destinationDesc = renderGraph.GetTextureDesc(source);
destinationDesc.name = $"CameraColor-{m_PassName}";
destinationDesc.clearBuffer = false;
destinationDesc.colorFormat = GraphicsFormat.R8G8B8A8_UNorm; // ensure alpha
TextureHandle destination = renderGraph.CreateTexture(destinationDesc);
RenderGraphUtils.BlitMaterialParameters para1 = new(source, destination, m_BlitMaterial, 0);
renderGraph.AddBlitPass(para1, passName: m_PassName+"_1");
RenderGraphUtils.BlitMaterialParameters para2 = new(destination, source, m_BlitMaterial, 1);
renderGraph.AddBlitPass(para2, passName: m_PassName + "_2");
}
}
public class GaussianOutlineRenderFeature : ScriptableRendererFeature
{
[Tooltip("The material used when making the blit operation.")]
public Material material;
[Tooltip("The event where to inject the pass.")]
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
GaussianOutlineRenderPass m_Pass;
public override void Create()
{
m_Pass = new GaussianOutlineRenderPass();
m_Pass.renderPassEvent = renderPassEvent;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (material == null)
{
Debug.LogWarning(this.name + " material is null and will be skipped.");
return;
}
m_Pass.Setup(material);
renderer.EnqueuePass(m_Pass);
}
}
I find the documentation of the Render Graph quite lacking and confusing, the only thing I needed was 2 blit passes and 1 temporary texture, and it still took me like 2 days to just make it work.