Quick answer: Every GetTemporary needs a ReleaseTemporary. Every new RenderTexture needs Release and Destroy. Cache and reuse instead of allocating per frame, and check the Memory Profiler for growing RenderTexture counts.

Here is how to fix Unity RenderTexture memory leaks. Your game runs fine for 10 minutes, then frame rate tanks. The Profiler shows GPU memory climbing at roughly one RenderTexture per second. You look for textures being loaded and not unloaded — but nothing. The leak is RenderTextures, not ordinary textures, and they are nearly invisible in the standard Memory Profiler view unless you know where to look. RenderTextures cost a lot and their lifetime rules are different from other assets.

The Symptom

GPU memory steadily grows during play. The Memory Profiler’s Graphics category increases by 4–16 MB per minute depending on texture size. After enough time, the game either crashes with an out-of-memory error, hits a hard frame rate floor, or triggers the OS’s GPU process kill on mobile. On desktop with 8 GB+ VRAM the leak may never visibly impact gameplay but still fails memory certification and causes low-end systems to struggle.

Opening the Memory Profiler (Package Manager > Memory Profiler) and filtering by RenderTexture reveals dozens or hundreds of RenderTexture objects, many with zero references, many labeled “Temporary” or bearing temporary-looking names.

What Causes This

Unreleased GetTemporary calls. RenderTexture.GetTemporary returns a pooled texture. Unity expects you to call ReleaseTemporary on it when you are done. If you forget, the texture stays allocated forever. This is the single most common RenderTexture leak — particularly in custom post-processing code, compute shader dispatches, and image effects.

New RenderTexture per frame. A script that does var rt = new RenderTexture(...) in Update allocates a fresh texture every frame. If nothing releases the previous one, the count grows indefinitely. Garbage collection eventually drops the managed wrapper, but the native GPU memory can remain pinned.

Release without Destroy. Calling rt.Release() frees the GPU memory but leaves the managed RenderTexture object alive. If you later lose all references to it without calling Destroy, the object becomes unreachable but may not be immediately collected. This is less severe than a native leak but still wastes managed memory.

Destroy without Release. Calling Destroy(rt) should free both managed and native memory, but Unity has had bugs where destroying a RenderTexture that was actively bound to a camera or material did not immediately release GPU memory. Explicitly Release-then-Destroy avoids this.

Retained by materials. A material with a _MainTex bound to a RenderTexture keeps that RenderTexture alive. Destroying the RenderTexture while a material still references it leaves a dangling pointer on some platforms. Unbind (material.mainTexture = null) before destroying.

The Fix

Step 1: Pair GetTemporary/ReleaseTemporary rigorously. Every path through your code that calls GetTemporary must call ReleaseTemporary — including error paths and early returns. Use try/finally for safety.

using UnityEngine;

public class PostEffect : MonoBehaviour
{
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        RenderTexture tmp = RenderTexture.GetTemporary(
            src.width, src.height, 0, src.format);

        try
        {
            Graphics.Blit(src, tmp, effectMaterial);
            Graphics.Blit(tmp, dest);
        }
        finally
        {
            RenderTexture.ReleaseTemporary(tmp);
        }
    }
}

The try/finally ensures that even if the Blit throws, the temporary texture is returned to the pool. Without it, a single exception leaks that temporary forever.

Step 2: Cache long-lived RenderTextures. If you need a RenderTexture for more than one frame, allocate once in Awake or on first use, and reuse. Destroy in OnDestroy.

private RenderTexture rt;

void Awake()
{
    rt = new RenderTexture(512, 512, 0,
        RenderTextureFormat.ARGB32);
    rt.Create();
}

void OnDestroy()
{
    if (rt != null)
    {
        rt.Release();
        Destroy(rt);
        rt = null;
    }
}

Never allocate a persistent RenderTexture inside Update. If you need different sizes depending on state, either use GetTemporary for transient access or check whether the cached size matches before reallocating.

Step 3: Unbind before destroying. Clear references held by materials and cameras before destroying a RenderTexture.

void DisposeRT(RenderTexture r)
{
    if (r == null) return;

    // Unbind from any material that might still reference it
    material.mainTexture = null;

    // Unbind from camera target if applicable
    if (targetCam != null)
        targetCam.targetTexture = null;

    r.Release();
    Destroy(r);
}

Step 4: Audit with Memory Profiler. Install the Memory Profiler package. Take a baseline snapshot at game start. Play for a few minutes. Take another snapshot. Diff the two and filter by RenderTexture. Any RenderTextures that exist in the second snapshot but not the first, and are not currently in active use, are leaks.

Screen Capture Pattern

If you implement screenshot capture (for bug reports, for example) via a RenderTexture, that is a common leak source. Allocate, blit, read back, then always release:

public Texture2D CaptureScreenshot()
{
    RenderTexture rt = RenderTexture.GetTemporary(
        Screen.width, Screen.height, 24);
    Camera.main.targetTexture = rt;
    Camera.main.Render();

    RenderTexture.active = rt;
    Texture2D tex = new Texture2D(Screen.width, Screen.height);
    tex.ReadPixels(new Rect(0,0,Screen.width,Screen.height), 0,0);
    tex.Apply();

    Camera.main.targetTexture = null;
    RenderTexture.active = null;
    RenderTexture.ReleaseTemporary(rt);

    return tex;
}

Mobile Considerations

On mobile GPUs, RenderTexture memory is not always distinct from system memory (unified memory architectures). A RenderTexture leak on Android can trigger the Low Memory Killer before you notice in Unity’s Profiler. Test long-running sessions on actual devices, not just desktop. The Adreno Profiler and Xcode Instruments show GPU memory by frame.

“Every render texture you create is a promise to release it. Break the promise and the GPU remembers forever.”

Related Issues

For general texture leaks, see Unity Memory Leak Texture Not Releasing. For GPU readback-specific issues, AsyncGPUReadback Returning Empty covers related render target issues.

GetTemporary/ReleaseTemporary. Release/Destroy. These pairs are not optional.