Unity 4gb texture limit: If a scene or asset is NOT in a bundle and references a texture that is, will it load from the asset bundle?

I’m trying to solve this problem:

~https://discussions.unity.com/t/bug-4gb-limit-to-textures-in-standalone-build/645929~

When I run the game in editor everything is fine. But when I build a standalone build all of the textures are corrupted. They are all spliced together and the emission is turned up so high that it’s blinding if bloom is turned on. And every cube map is particularly corrupted, just a mess of color noise, regardless of whether they are being used for reflection maps or skyboxes.

Based on my research this is because unity doesn’t support more than 4gb of texture data. However, the documentation is saying limitation can be avoided by using Asset bundles, since each asset bundle can support up to 4gb worth of texture data. So if I split the textures into separate bundles I should be able to build with more than 4gb worth of texture data, so long as no one bundle goes over that 4gb limit. Or at least that was my understanding.

I have the asset bundles split up pretty small and manually checking them I’m sure none of them are more than 4gb total, uncompressed. And yet the textures are still corrupted in the stand alone build.

I have packed all of our large textures into asset bundles, built bundles (placing them in the streaming assets folder so they just ship with the game), then re-built the game. But the issue persists.

I’m struggling to understand why this is still happening.

At first I thought it was because the main menu scene contains references to meshes and materials that use those textures. But I tried deleting those objects from the scene, saving, and re-building and the issue still persists.

I’m wondering if Unity is duplicating the texture assets even though I’m telling them to be placed in bundles.

Based on the documentation here:~https://docs.unity3d.com/Manual/AssetBundles-Dependencies.html#:~:text=To%20avoid%20data%20duplication%2C%20assign,automatically%20during%20the%20build%20process~.

To avoid data duplication, assign the Material and its referenced Assets to its own modulesmaterials AssetBundle. You can also tag the Material only, because the Texture dependency is also included in the AssetBundle automatically during the build process.

So do I just have to pack the materials alongside the textures?

Do I have to pack every level and everything contained in that level into an asset bundle?

Or the materials, textures and meshes? Prefabs?

Or can unity literally not handle loading more than 4gb worth of textures at any given time, regardless of whether they are split up into multiple bundles?

I struggle to believe that last one since it seems like a catastrophic limitation for modern games, but I’m really not clear on why this is still happening after all my large textures have been packed into bundles.

The only thing I know for certain works is deleting the texture folders for the new assets we added to the game recently, which is what caused this to start happening in the first place. So I know for a fact that this is related to the textures, but that’s obviously not a real solution.

Generally, Unity will NOT automatically reference things between bundles and scenes. If you put the scene itself into a bundle, that scene will automatically either reference other bundle assets or will create a copy of things not explicitly in a bundle, to put in it’s own bundle.

You either need to write your own bundle manager (or use addressables) and write your own asset bundle reference helpers, or just throw EVERYTHING into bundles and call it a day.

For example, if you want to have a scene with a prefab inside, and that one prefab in a bundle, you need to do something like this: Write a script which references a bundle asset, and then spawn that asset at runtime. OR, you can just put the whole scene in a bundle, then load the scene from the bundle.

What I don’t understand is that I tried editing the scene so the prefabs with the offending textures were no longer included. In fact, in it’s current state those prefabs aren’t in any scene at launch. Instead they are added via instantiate after the scene has loaded.

I expected that to either load them from the bundles automatically, or throw a null ref if the bundles don’t load automatically.

Am I to understand what is actually happening is Unity is detecting that the script will spawn the object in the future (I guess the prefab is slotted into one of the monbehaviors saved tot he main menu scene to tell it what to spawn) and then duplicating all of that prefabs assets? (and thus the textures that are too big to fit?)

You’re probably doing something like:

public GameObject myPrefab;
//
var myGameObject = GameObject.Instantiate(myPrefab);

The reference to the prefab will be a HARD reference, within the scene. When Unity builds asset bundles (or when it builds scenes for built in resource management) it will scan down the hierarchy of bundles or scenes to build a dependency graph. It does not automatically go “oh, this is in a bundle” and magically swap out a hard reference for you. When you use Addressables or your own asset management system, you’ll have something like a SOFT AssetReference instead, with a different API for loading the prefab from the bundle and then you instantiate that prefab instead.

tldr if youre not doing something like the following, you aren’t actually loading from an asset bundle:

public AssetReference<GameObject> myPrefabReference;
//
var myPrefab = YourAssetBundleManager.LoadAsset(myPrefabReference);
var myGameObject = GameObject.Instantiate(myPrefab); 
2 Likes

Ah ok! I think that’s the part I needed next. I wasn’t sure how I was going to reference the prefab/bundle I wanted it to load without… referencing it. So AssetRefrence is how I would do that. Then create an instance of the loaded prefab to actually use in game. If I’m understanding correctly.

Working on testing a fix now. If it works I’ll mark your post as the solution thanks!

Ok, running into some serious problems with implementing this. AssetRefrence seems to only exist in the context of the Addressables package.

I was able to instal the addresables package which converted all my bundles over to addressables, then refrence the objects in them and spawn them. And it did in fact solve my problem.

However. There is no way to load an asset synchronously. There is both a LoadAssetAsync and a LoadAsset function, But loadAsset is depricated AND still doesn’t actually return an object but an AsyncOperationHandle so it’s ALSO asyncronus.

There are many points in my game, specifically when it’s first starting up so I’m quite comfortable eating an extra second or two for load time, where I load and activate stuff in the main thread. And it would be a massive refactor to overhaul that so it waits for these Async functions.

Based on the documentation I’m reading the old AssetBundle system did have a way to load synchronously, But I’m failing to find any documentation on how to actually reference and then isntantiate a prefab from within an AssetBundle.

Right, that’s why I mentioned you may be interested in building your own or looking for other solutions. Addressables is actually pretty rough. I made one here, feel free to take a look: GitHub - eddietree/FunkAssetBundles

[Solution]

Ok I finally got this working using Addressables.

First I assigned all the assets related to the new feature, in my case a set of new enemies in the game, to bundles. I included the actual prefab I wanted to spawn, as well as the meshes used in that prefab, the materials assigned to the mech, and the textures assigned to those materials.

This was done by selecting the asset then selecting assign or add to bundle from a drop down in the inspector.

In my case this was before I had implemented the Addressables system so I’m afraid I can’t give an explanation on that.

Once I installed Addressables from the package manager it asked me if I wanted to convert all my existing bundle assignments to Addressables and I hit confirm and it created all the groups for me.

Once all my assets were assigned to addressable groups, I then built them by going to Window > Asset Management > Addressables > Groups, then clicking the build > New Build > Default script. Fair warning this takes like 20-40 minutes, at least when starting from nothing.

Once the bundles… addressables? Were built I could then drop the prefabs into AssetRefrence variables on monobehaviours the same way I would slot a prefab into a GameObject variable.

To add such a reference I had to add

using UnityEngine.AddressableAssets; 

to my script, then add a

public AssetReference PrefabAssetRefrence;

In my case I also kept the original

public GameObject Prefab; 

Because I wanted my script to support doing both normal prefabs and asset reference. So I wrote them so if the PrefabAssetRefrence was populated, it would load the prefab from that reference into the Prefab variable so the rest of the script could continue going on normally.

The core part of this implementation was loading the assets from the bundles. And also caching them so we didn’t waste time on an expensive load operation if the asset was already loaded. My understanding is that calling LoadAssetAsync won’t create duplicate entries in memory, however it’s still much slower than just pulling the asset out of a dictionary.

This is what I went with for my core loading and unloading code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public static class BundledAssetManager
{
    public static Dictionary<string, AsyncOperationHandle> LoadedAssets = new Dictionary<string, AsyncOperationHandle>();
    public static Dictionary<string, GameObject> LoadedGameObjects = new Dictionary<string, GameObject>();

    public static GameObject LoadPrefabAsset(AssetReference asset)
    {
        if(LoadedGameObjects.TryGetValue(asset.AssetGUID, out GameObject existingAsset))
        {
            return existingAsset;
        }
        else
        {
            AsyncOperationHandle<GameObject> operation = Addressables.LoadAssetAsync<GameObject>(asset);
            operation.WaitForCompletion();

            if (operation.Status == AsyncOperationStatus.Succeeded && operation.Result != null)
            {
                LoadedAssets[asset.AssetGUID] = operation;
                LoadedGameObjects[asset.AssetGUID] = operation.Result;
                return operation.Result;
            }
            else
            {
                Debug.LogError("Failed to spawn asset " + asset.SubObjectName);
                return null;
            }
        }
    }

    public static void UnloadAsset(AssetReference asset)
    {
        //Remove refrence to the object
        LoadedGameObjects.Remove(asset.AssetGUID);

        //Release the asset
        AsyncOperationHandle assetHandle;
        if(LoadedAssets.TryGetValue(asset.AssetGUID, out assetHandle))
        {
            Addressables.Release(assetHandle);
        }
    }
}

I’ll be honest I don’t actually know if the UnloadAsset() function works. I wrote it to cover my bases but in my case I’m not using it so far. The core idea behind the rest of the script is two dictionaries, and a load function.

The dictionaries identify AssetRefrences by their guid strings. When the load function is called it first checks if we already have that prefab loaded and if so returns it from the dictionary without calling the load function.

If the asset is not loaded then it loads it and caches it. Also caching the AsyncOperationHandle because that’s what’s needed to release the asset reference if and when.

The big important thing here is that operation.WaitForCompletion(); allows the otherwise async load to be used synchronously. And thus didn’t require me to refactor my entire code.

I had tested it before but it seemed to lock up the game. But that lock up was due to it actually working correctly, and then spawning the 5 physics objects I requested in the same spot causing a physics based lag spike, which sent me down the wrong rabbit hole thinking that calling WaitForCompletion() was locking up the game.

So with that script in place to instantiate a prefab from an addressable bundle I would do:

GameObject prefab = BundledAssetManager.LoadPrefabAsset(PrefabAssetRefrence);
Instantiate(prefab) as GameObject;

Altogether this has in fact solved my issue with blowing up the texture limit, and my project is back on track. Hopefully this helps anyone else running into the same issue.