Search Unity

Bug RTHandles API introduced catastrophic memory leak bug in 2022.3.8

Discussion in 'Universal Render Pipeline' started by ElliotB, Aug 30, 2023.

  1. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    Hi all,

    It seems there was a very large memory leak bug introduced in 2022.3.8 (on my PC it went as far as filling 32GB RAM and reserving a 32GB page file). The leak does not occur in 2022.3.7, which leads me to presume it is a bug rather than me incorrectly using the API - I'm very happy to be told otherwise.

    (I'm developing on windows 11)

    Steps to reproduce

    1. Create a new URP project using the core template in 2022.3.8f1.
    2. Create the MemoryLeakRenderFeature, source below.
    3. Add the MemoryLeakRenderFeature to your render pipeline asset's renderer.
    4. Select the main camera in your scene to bring up the inset camera preview on the scene tab, and right click + move the mouse to spin the scene camera and cause the scene tab to redraw. OR open a material inspector, and spin the material preview window around to cause it to redraw.
    5. After a few seconds, Unity will start to hemorrage (see graph below).

    Code (CSharp):
    1. using UnityEngine.Rendering;
    2. using UnityEngine;
    3. using UnityEngine.Rendering.Universal;
    4.  
    5. namespace MemoryLeakExample
    6. {
    7.     public class MemoryLeakRenderFeature : ScriptableRendererFeature
    8.     {
    9.         MemoryLeakPass Pass;
    10.         MemoryLeakSecondPass SecondPass;
    11.  
    12.         public override void Create()
    13.         {
    14.             Pass = new MemoryLeakPass();
    15.             SecondPass = new MemoryLeakSecondPass();
    16.         }
    17.  
    18.         public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    19.         {
    20.             renderer.EnqueuePass(Pass);
    21.             renderer.EnqueuePass(SecondPass);
    22.         }
    23.  
    24.         public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    25.         {
    26.         }
    27.  
    28.         protected override void Dispose(bool disposing)
    29.         {
    30.             base.Dispose(disposing);
    31.             if (disposing)
    32.             {
    33.                 Pass.Dispose();
    34.             }
    35.         }
    36.     }
    37.  
    38.     public class MemoryLeakPass : ScriptableRenderPass
    39.     {
    40.         public MemoryLeakPass()
    41.         {
    42.             renderPassEvent = RenderPassEvent.BeforeRenderingOpaques;
    43.         }
    44.  
    45.         RTHandle _SomeRT;
    46.         RTHandle _SomeDepthRT;
    47.         RTHandle _AnotherRT;
    48.  
    49.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    50.         {
    51.             base.OnCameraSetup(cmd, ref renderingData);
    52.         }
    53.  
    54.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    55.         {
    56.             var descriptor = cameraTextureDescriptor;
    57.             var depthDescriptor = descriptor;
    58.             descriptor.depthBufferBits = 0;
    59.             RenderingUtils.ReAllocateIfNeeded(ref _SomeRT, descriptor, name: "_SomeRT");
    60.             RenderingUtils.ReAllocateIfNeeded(ref _SomeDepthRT, depthDescriptor, name: "_SomeDepthRT");
    61.             RenderingUtils.ReAllocateIfNeeded(ref _AnotherRT, descriptor, name: "_AnotherRT");
    62.         }
    63.  
    64.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    65.         {
    66.             CommandBuffer buffer = CommandBufferPool.Get(nameof(MemoryLeakPass));
    67.             context.ExecuteCommandBuffer(buffer);
    68.             CommandBufferPool.Release(buffer);
    69.         }
    70.  
    71.         public void Dispose()
    72.         {
    73.             _SomeRT?.Release();
    74.             _SomeDepthRT?.Release();
    75.             _AnotherRT?.Release();
    76.         }
    77.     }
    78.  
    79.     public class MemoryLeakSecondPass : ScriptableRenderPass
    80.     {
    81.         public MemoryLeakSecondPass()
    82.         {
    83.             renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
    84.         }
    85.         RTHandle _RT;
    86.  
    87.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    88.         {
    89.             base.OnCameraSetup(cmd, ref renderingData);
    90.         }
    91.  
    92.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    93.         {
    94.             RenderingUtils.ReAllocateIfNeeded(ref _RT, cameraTextureDescriptor, name: "Some_Unrelated_Name");
    95.         }
    96.  
    97.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    98.         {
    99.             CommandBuffer buffer = CommandBufferPool.Get(nameof(MemoryLeakPass));
    100.             context.ExecuteCommandBuffer(buffer);
    101.             CommandBufferPool.Release(buffer);
    102.         }
    103.  
    104.         public void Dispose()
    105.         {
    106.             _RT?.Release();
    107.         }
    108.     }
    109. }
    Example memory use in 2022.3.8:
    upload_2023-8-30_0-27-24.png
    Example memory use in 2022.3.7:
    upload_2023-8-30_0-44-33.png


    Additional details:

    1. The leak will not occur if only the first pass or the second pass are enqueued. Both are required.
    2. The leak will not occur if only _SomeRT is ReAllocateIfNeeded in the first pass. You need at least _SomeRT and _SomeDepthRT, and if you ReAllocateIfNeeded those you will get a small memory leak. Changing the order to
    Code (CSharp):
    1.             RenderingUtils.ReAllocateIfNeeded(ref _SomeRT, descriptor, name: "_SomeRT");
    2.             RenderingUtils.ReAllocateIfNeeded(ref _AnotherRT, descriptor, name: "_AnotherRT");
    3.             RenderingUtils.ReAllocateIfNeeded(ref _SomeDepthRT, depthDescriptor, name: "_SomeDepthRT");
    also reduces the rate of the leak. ReAllocateIfNeeded on color targets, then on depth targets, leaks more slowly than intermingled color/depth targets.
     
    tsukimi and sordidlist like this.
  2. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    It looks like 2022.3.8 introduced pooling for RTHandles, which seems the likely cause for the leak.
     
  3. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    Report:
    IN-53195
     
  4. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    I believe this may be the cause of the bug:

    2022.3.8f1 ReAllocateIfNeeded includes the following code:
    Code (CSharp):
    1.                 if (handle != null && handle.rt != null)
    2.                 {
    3.                     TextureDesc currentRTDesc = RTHandleResourcePool.CreateTextureDesc(handle.rt.descriptor, TextureSizeMode.Scale, handle.rt.anisoLevel, handle.rt.mipMapBias, handle.rt.filterMode, handle.rt.wrapMode);
    4.                     UniversalRenderPipeline.s_RTHandlePool.AddResourceToPool(currentRTDesc, handle, Time.frameCount);
    5.                 }
    6.  
    7.                 if (UniversalRenderPipeline.s_RTHandlePool.TryGetResource(requestRTDesc, out handle))
    8.                 {
    9.                     return true;
    10.                 }
    11.                 else
    12.                 {
    13.                     handle = RTHandles.Alloc(scaleFactor, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
    14.                     return true;
    15.                 }
    My hunch is the following:
    When the number of stale resources in the pool exceeds the pool's capacity, the AddResourceToPool will fail for a non-null RTHandle. RTHandle remains non-null and allocated, and AddResourceToPool returns null. The following TryGetResource(out handle) will wipe the reference to the previously allocated RTHandle. If TryGetResource returns false, a new handle will be allocated in RTHandles.Alloc. When the next frame is drawn, the process repeats - AddResourceToPool fails, the handle is lost without deallocation, memory is leaked.

    Compare this to the equivalent code in 2022.3.7f1:
    Code (CSharp):
    1.                 handle?.Release();
    2.                 handle = RTHandles.Alloc(descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
    This code releases the handle before allocating a new resource.
     
  5. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    This is the fix:

    Change line 600 in Runtime/RenderingUtils.cs:
    Code (CSharp):
    1. UniversalRenderPipeline.s_RTHandlePool.AddResourceToPool(currentRTDesc, handle, Time.frameCount);
    To this:
    Code (CSharp):
    1. if (!UniversalRenderPipeline.s_RTHandlePool.AddResourceToPool(currentRTDesc, handle, Time.frameCount))
    2.     handle?.Release();
     
    LiterallyJeff, Meatloaf4 and Timboc like this.
  6. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,313
    Why Unity. Why
     
    AlonMixed likes this.
  7. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    234
    Thanks for your report! The fix is landing soon and will be available in one of the next patch releases.
     
  8. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    Thanks @ElliotB for finding this and sharing your solution (and @AljoshaD for the progress update).
     
    Meatloaf4 likes this.
  9. bingchill0

    bingchill0

    Joined:
    Mar 10, 2023
    Posts:
    1
    @ElliotB, you're an absolute champ!!! This was exactly the issue that was crashing my pc when previewing animations and models. Thank you!
     
    AlonMixed and Meatloaf4 like this.
  10. ThomasZeng

    ThomasZeng

    Unity Technologies

    Joined:
    Jun 24, 2019
    Posts:
    86
    adamgolden, JesOb and ElliotB like this.
  11. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    It appears the fix didn't make it into 2022.3.9f1, and the leak is still present. I see there's a fix under review for 2022.3.10f1, so fingers crossed!
     
    AlonMixed, ThomasZeng and JesOb like this.
  12. Anibaaal

    Anibaaal

    Joined:
    Aug 15, 2021
    Posts:
    2
    I've been having Depth and Opaque textures related memory leaks in my project with 2022.3.3f1, 4, 7, 8 and 9f1 and this but is still present! This happens in the editor, if in URP settings I have those textures enabled it will start eating up memory until it crashes my system.
    If i disable them, the leak stops but the memory is still used.
    My scene only has one camera and I don't have any render textures in my assets.

    Is it possible that it's related to this?
     
  13. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    Not if it occurs in 2022.3.7f1 aswell, because this bug arises from the introducing of RTHandle pooling that was introduced in 2022.3.8f1
     
    AlonMixed likes this.
  14. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    989
    Just for the record this issue is still present in Unity 2022.3.10f1.
     
  15. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    989
    Apologies, it appears the issue is in fact resolved in this version. The memory leak is no longer occuring. I am however still getting errors in my console due to certain settings in one of my off-screen my Cameraa. I should be able to fix this by making the necessary changes to that. Sorry for the confusion.
     
    ElliotB likes this.
  16. Roman-Ilyin

    Roman-Ilyin

    Joined:
    Oct 9, 2013
    Posts:
    29
    Issue still present in Unity 2022.3.11f1.
     
    nasos_333 likes this.
  17. ThomasZeng

    ThomasZeng

    Unity Technologies

    Joined:
    Jun 24, 2019
    Posts:
    86
    Hey @Roman-Ilyin ,
    Leak issues that we are aware of are resolved.
    If you still see it, please report a bug with repro project. We can then investigate and take actions.

    Thanks,
    Thomas
     
    nasos_333 likes this.
  18. cnielsen1

    cnielsen1

    Joined:
    Feb 11, 2020
    Posts:
    1
    The leak is still happening! Just upgraded to 2022.3.12.f1, yesterday and NOT happy!
    Internal: Stack allocator ALLOC_TEMP_MAIN has unfreed allocations, size 118
    I will be rolling back to Unity 2021!
     
    nasos_333 likes this.
  19. Roman-Ilyin

    Roman-Ilyin

    Joined:
    Oct 9, 2013
    Posts:
    29
    In my case, the problem does not reproduced in 2022.3.12 and 2022.3.13, but the problem persisted in 2023.1.20 (IN-61609) and 2023.2.0 (IN-61603) versions.
     
    Last edited: Nov 23, 2023
    nasos_333 likes this.
  20. nasos_333

    nasos_333

    Joined:
    Feb 13, 2013
    Posts:
    13,370
    Hi, is this something that is still happening in some versions though ?

    I just got a 1 star review on my Ethereal asset, mainly i suppose due to the frustration because of a memory crash leak, which i cannot replicate in 2021.3.29 or 2022.3.13 where i work.

    Is it possible to have a detailed answer as to which Unity versions are still affected and not to be used ? Would also be useful if you remove them from the Hub and make some announcement about such a vast issue right inside the Hub, as renders Unity unusable for some versions that should never be used.
     
  21. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    291
    AFAIK it's 2022.3.8 and 2022.3.9 that are affected. I'm not sure about 2023 as I wasn't using it at the time
     
    nasos_333 likes this.