Memory Leak in BuildPlayerContent with SpriteAtlases

We’re having issues running BuildPlayerContent on our project with Addressables. We’re using Editor 2021.3.18f1 and Addressables 1.19.17 to build on MacOS 13.2.1. When we do a build the editor consumes all available memory during the “Write Serialized Files build phase while writing “archive:/CAB-*” files (craps out around 87 GB on my machine). The build does work on installations that have performed incremental builds and that only need to update some of the files, but we cannot build at all if the Library is not up to date.

I did some investigating, and it looks like most of the memory is instances of Texture2D. If I follow the reference chain these are held by SpriteAtlasDatabase (and they don’t go away after the build). Most of our bundles only contain SpriteAtlases, and we’re always adding more.

If I edit ScriptableBuildPipeline and disable the call to Editor/Tasks/WriteSerializedFiles.cs:ProcessUncached() > Editor/WriteTypes/AssetBundleWriteOperation.cs:Write() > ContentBuildInterface.WriteSerializedFile() the leak does not happen and the build fails elsewhere (presumably because the files haven’t been created), so the leak is happening in WriteSerializedFile, which is in the UnityEditor.Build.Content package.

It looks like this is a version of several other similar leaks in the Addressables build chain reported by other users here , here , here , here, and here .

Is there any configuration option or bug fix that will prevent WriteSerializedFile() from loading all textures? Is there anything we can call to get SpriteAtlasDatabase to release the Textures once they’re loaded? I’m not sure if we’ve set up our project incorrectly or if this is a bug.

Hi, I think there is a workaround might work: after ContentBuildInterface.WriteSerializedFile is called in AssetBundleWriteOperation.Write, you can determine if that asset bundle includes or references to any SpriteAtlas by checking AssetBundleWriteOperation.Info, if it does, then call Resources.UnloadAsset for each SpriteAtlas to unload them.

Thanks for the suggestion. I’ve been trying to implement it, but I ran into an issue. AssetBundleWriteOperation.Info has a reference to the GUID and path, but not the actual object, which is what Resources.UnloadAsset needs. I did try adding a call to
Resources.UnloadUnusedAssets(), but that didn’t release my leak.

I’ve noticed another pattern while investigating this. The leaked textures aren’t the actual atlas pages – they’re the original textures that are packed into the atlases.

This explains why our project is being hit so badly. Our artists often supply us with very large images that are mostly blank (4096px x 1202px texture with just 12px x 12px of actual data). We never worried about these too much since the source image is compressed well because of the png compression, and the final texture is packed into a sprite atlas and only occupies a few pixels.

When WriteSerializedFile is triggered it loads (and leaks) the uncompressed version of the texture which adds up very quickly. The strange thing is that if I look in the memory inspector, all of the reference chains lead back to the SpriteAtlasDatabase, which has a reference count of 0, so my call to Resources.UnloadUnusedAssets() should clean it up.

You can try if the following code works for you:

 .../Editor/WriteTypes/AssetBundleWriteOperation.cs | 26 +++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/Packages/com.unity.scriptablebuildpipeline/Editor/WriteTypes/AssetBundleWriteOperation.cs b/Packages/com.unity.scriptablebuildpipeline/Editor/WriteTypes/AssetBundleWriteOperation.cs
index 31257ed..788d3e2 100644
--- a/Packages/com.unity.scriptablebuildpipeline/Editor/WriteTypes/AssetBundleWriteOperation.cs
+++ b/Packages/com.unity.scriptablebuildpipeline/Editor/WriteTypes/AssetBundleWriteOperation.cs
@@ -5,6 +5,7 @@ using UnityEditor.Build.Content;
using UnityEditor.Build.Pipeline.Interfaces;
using UnityEditor.Build.Pipeline.Utilities;
using UnityEngine;
+using UnityEngine.U2D;
namespace UnityEditor.Build.Pipeline.WriteTypes
{
@@ -33,7 +34,7 @@ namespace UnityEditor.Build.Pipeline.WriteTypes
         public WriteResult Write(string outputFolder, BuildSettings settings, BuildUsageTagGlobal globalUsage)
         {
#if UNITY_2019_3_OR_NEWER
-            return ContentBuildInterface.WriteSerializedFile(outputFolder, new WriteParameters
+            var writeResult = ContentBuildInterface.WriteSerializedFile(outputFolder, new WriteParameters
             {
                 writeCommand = Command,
                 settings = settings,
@@ -42,6 +43,29 @@ namespace UnityEditor.Build.Pipeline.WriteTypes
                 referenceMap = ReferenceMap,
                 bundleInfo = Info
             });
+
+            List<SpriteAtlas> atlases = null;
+            foreach (var asset in Info.bundleAssets)
+            {
+                string path = AssetDatabase.GUIDToAssetPath(asset.asset);
+                if (path.EndsWith(".spriteatlas", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (atlases == null)
+                        atlases = new List<SpriteAtlas>();
+
+                    atlases.Add(AssetDatabase.LoadAssetAtPath<SpriteAtlas>(path));
+                }
+            }
+
+            if (atlases != null)
+            {
+                foreach (var atlas in atlases)
+                    Resources.UnloadAsset(atlas);
+
+                Debug.Log("Unload atlases");
+            }
+
+            return writeResult;
#else
             return ContentBuildInterface.WriteSerializedFile(outputFolder, Command, settings, globalUsage, UsageSet, ReferenceMap, Info);
#endif

Thanks. I will try that once I get out of my current rabbit hole. I’m trying to use a AssetProcessor to crop all of our assets on import so we don’t have such large intermediate data. But it would also be great if the size of the intermediate data didn’t matter while building. The post processing is working, but I’m having scaling issues that I’m trying to resolve with pixelsPerWorldUnit. Once I get that working I’ll try the code above.

@davidla_unity

I’ve finally tried this example, and it works – but I can’t tell if it fixes the problem or not, because I can’t reproduce it anymore. Even without this code and my other fix for our issue I can’t get my builds to use up too much memory in my test project or even in our production project. I can’t figure out what’s changed, since before I couldn’t get a single build to complete. In my test project I couldn’t even pack textures into an atlas because I’d run out of swap. I’ve wiped out my Library multiple times and also switched between scriptable build pipeline 1.20.1 and 1.21.2.

The solution I mentioned above (reducing the size of our intermediate textures by cropping out whitespace) solved the build problem as well as the size of our Library/Artifacts folder. It helps to correct a part of our workflow that was causing issues, but it is pretty hacky.