TextMesh Pro: Font assets duplicated in memory (Resources & AssetBundle)

TextMesh Pro requires font assets to be placed in a Resources magic folder:

We have to use asset bundles in our project. This means assets are pulled-in to asset bundles through Unity’s dependencies system.

This initially caused that a lot of asset bundles pulled in a copy of all TextMesh Pro font assets from the resources folder, wasting a lot of disk and runtime memory.

In order to avoid that these font assets are stored in more than one asset bundle, I marked the “Resources/Fonts” directory to be stored in a separate asset bundle too.

This reduced the number of copies of the same font assets to “only two”. Two because one copy comes from the asset bundle and the other one from the Resources folder.

How can I get rid of the second copy in memory? I only want to have TextMesh Pro font assets in memory once.

2 Likes

Are you referencing any of these Font Assets in the text itself using the <font=“Name of Font Asset”> tag?

That is pretty much the only reason why Font Assets need to be contained in a Resources folder.

1 Like

Thanks for the quick reply. I don’t think we use the font-tag yet, but I know that we do use the sprite-tag.

Have you thought about adding a callback to TextMeshPro, to allow usercode to load a requested TextMesh Pro asset? I believe you could implement that with full backwards compatibility.

Here is the idea that popped into my head. It’s full backwards compatible and it seems it would be a few and simple code changes only to the TMP code.

In TMP_Settings or wherever you see fit, add a callback where the user can add his/her custom loading method. In order for TextMesh Pro to load resources internally, it does not call Resources.Load() anymore, but TMP_Settings.LoadResource().

public class TMP_Settings : ScriptableObject
{
    public static System.Func<string, System.Type, UnityEngine.Object> loadResource;

    internal static T LoadResource<T>(string assetName) where T : UnityEngine.Object
    {
        T result = default;

        if (loadResource != null)
            result = loadResource(assetName, typeof(T)) as T;

        if (result == null)
            result = Resources.Load<T>(defaultFontAssetPath + assetName);

        return result;
    }
}

In our game code, we could then do things like:
Code

[ExecuteInEditMode]
[DefaultExecutionOrder(int.MinValue)]
public class GameTextMeshProAssetManager : MonoBehaviour
{
    void Awake()
    {
        GameObject.DontDestroyOnLoad(gameObject);
    }

    void OnEnable()
    {
        TMP_Settings.loadResource += OnLoadTextMeshProResource;
    }

    void OnDisable()
    {
        TMP_Settings.loadResource -= OnLoadTextMeshProResource;
    }

    UnityEngine.Object OnLoadTextMeshProResource(string assetName, System.Type assetType)
    {
#if UNITY_EDITOR
        if (Application.isEditor)
        {
            string assetPath = "";

            if (assetType == typeof(ScriptableObject))
                assetPath = string.Format("Assets/Fonts/{0}.asset", assetName);

            if (string.IsNullOrEmpty(assetPath))
            {
                string[] guids = UnityEditor.AssetDatabase.FindAssets(string.Format("t:{0} {1}", assetType.Name, assetName));
                if (guids.Length > 0)
                    assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[0]);
            }

            return UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, assetType);
        }
#endif

        var ab = GetOrLoadAssetBundle("font_assets");
        return ab.LoadAsset(assetName, assetType);
    }
}

What do you think? Could this be a short-time solution to support loading TextMesh Pro assets from places outside the resources folder?

1 Like

@Peter77 This is a great idea.
Since I don’t see @Stephan_B answered, how did you solve the duplication issue?

I believe I didn’t solve it.

@Stephan_B I would appreciate your help to solve the font asset duplication issue, as described by @Peter77 above.

The duplication is typically the result of how the AssetBundle is setup. For instance, if you bundle 2 font assets, each bundle will include the shaders thus resulting in duplication of resources. To avoid this, you would create a separate bundle that only includes the shaders where the other two bundles have a dependency on this shader bundle.

Can you provide more information / some images showing how your bundle is setup?

Hi @Stephan_B ,

Problem description:

  • TMP Settings asset file is located under Assets\TextMesh Pro\Resources folder, which means it is embedded within the build target.
  • TMP Settings is referencing a Default Font Asset file (Resides in Assets\Fonts and contains a font atlas texture). Due to this reason, the font asset file is also embedded in the build, and loaded into memory on startup.
  • In addition, the same Default Font Asset file in included in an asset bundle. The reason for that, is that lots of prefabs are using this font file and they are a part of an asset bundle, so if this file is not included in an asset bundle it will automatically be added to their asset bundle (and in case of multiple asset bundle, it may be duplicated many times).

Possible solution, please review the following suggestion:

  • During editor time, TMP Settings will continue to reference the default font asset file (so that new text objects use it).
  • Before building a target version, this reference will be deleted (Can’t be done in code, please add support).
  • On application run time, after font asset file is loaded from asset bundle, startup code will set the default font asset file into TMP settings (Is this really needed??? Again support from your side is needed here)

This way there will only one copy of this asset in memory.

4 Likes

@Stephan_B I would love to hear your take on the above…

1 Like

+1.
waiting to @Stephan_B response

@Stephan_B
I have tried the suggested solution above (manually removing the reference before the build, because it is impossible by script for now) and it works. The duplication of font atlas in memory is gone.
It would be great if you can add support in code so the default font asset can be assigned and removed via code.

Thanks!

I have been reading and thinking about options.

Adding the ability to change some of the TMP Settings and assigned resources via scripting is certainly something I can add.

1 Like

Great!!
Just to make sure: The reference in TMP Settings to the default font asset is used only for editor time, is that correct?

When possible, please specify the TMPro version number which allows control over this field using script.
Thank you!

It is used in the Editor and at Runtime whenever a new text object is created.

So, two alternatives:

  1. As long as we don’t use AddComponent during runtime, we are safe.
  2. Alternatively, if we set the default font asset in TMP Settings after it is loaded from asset bundle (once scripting support is in place), we can also use AddComponent at runtime.

Hi @Stephan_B ,

Is this feature available in any preview version of TMPro?

Thanks,
Yaniv

I’ll try to add the ability to change these default assets in Preview 3.

1 Like

I’m in the middle of implementing Addressables and this made me aware again that TextMesh Pro relies on the Resources folder.

Are there any plans to support Addressables in TMP?

1 Like

No plans right now to support them directly in TMP.

However, I do plan on adding a TMP Resource Manager where users will be able to implement their own loading of TMP related resources from AssetBundles, Addressables or any other source and then to subsequently add them to the TMP Resource Manager which will enable TMP to use them regardless of how they were loaded or where they came from.

1 Like

Just two copies? Consider yourself lucky! :slight_smile:

We have a similar issue, in that we include the font asset (.otf, for a dynamic font) in an AssetBundle so that it will not be duplicated into multiple ABs. But it is. Despite being included in, say, “project_fonts”, it is getting included in every other AB that uses the TMP Font Asset. Since the particular font in question is for languages with larger character sets, it is huge (11MB), so our build is over 55MB bigger for no good reason that I can see, and as we add more content, it will just be duplicated more times.

I have created a separate, tiny, project to illustrate the problem. I have reproduced it in the latest 2019 and 2018 LTS builds, and submitted a bug (Case 1228188). @Stephan_B it would be great if you could take a quick look to at least ensure it is appropriately triaged.

2 Likes