Search Unity

TextMeshPro - Precull DoRebuilds performance

Discussion in 'UGUI & TextMesh Pro' started by Satochu, Oct 18, 2019.

  1. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Hello,

    I use textmeshpro in my application.
    Sometimes, textmeshpro eat a lot of framerate by rebuilding its textmeshpro. 2019-10-18_09-19-22.png

    2019-10-15_16-06-08.png I uploaded an example that show 278.44 ms of process.
    It's very huge and not acceptable.

    I'm working on 1.4.1
    Is it an internal lack of performance or are there some tips and tricks to avoid this spike ?

    Best regards

    Satochu
     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I'll need more information with regards to those text objects.

    How many text objects?

    Are you using dynamic font assets?

    How much text / number of characters does each contain?

    Are you using text auto size on all of them?

    Alternatively, you can submit a bug report with the project and steps for me to reproduce / check the behavior.
     
  3. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Hello,

    Unfortunately, I can't export a runnable projects (private business application).

    - I use something like 20 TextMeshPro.
    - I use some Dynamic font assets
    - The text isn't very long, the longuest one is like 40 characters. But The most texts are like 10 characters.
    - Everything is autosized
     
  4. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    I tried to find what is taking so much.
    I deleted 4 texts and get back like 100 ms.
    Here is a sample :

    upload_2019-10-18_11-46-35.png
     
  5. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Update:

    I removed Auto size and it seems to fix the performance problems.
    But I need autosize because my texts are translated.

    Is it a way to refresh label size without having autoSize checked ?
    Like this :

    MyTextMeshPro = "NewLabelLonger";
    MyTextMeshPro.RefreshSize();

    ???
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Text Auto Sizing requires multiple passes to determine the optimum point size for the given text container size. This can have significant performance overhead and has such I do not recommend using auto size on all text objects.

    In addition to performance overhead, using auto size on different text objects is likely to result in different point size which from a design point of view is not ideal. For instance, if you have 5 buttons in a UI, the text point size should be the same for all of them. It is fine to use different point size but similar text objects should use the same point size.

    My recommendation is to identify groups of text objects that should have the same point size. Then for each group, use one of the text objects, typically the one with the most characters to figure out the optimum point size. Then disabling auto size on this test object and then manually set the point size on it and the other text objects in that group. This ensures auto size is only used once per group of text objects.

    You should maintain a list of these groups and list of text objects within these groups. Whenever a new scene is loaded, you would iterate through the relevant groups to as per above find the optimum point size and then manually setting it on the other relevant text objects.

    In terms of changing point size, changing the .fontSize property will change the point size. On the object you are testing since you want this result within the same frame, you will have to use the ForceMeshUpdate() on the text object. As such you would ...

    Here is a very simple implementation example

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using TMPro;
    6.  
    7. public class TextAutoSizeController : MonoBehaviour
    8. {
    9.     public TMP_Text[] TextObjects;
    10.  
    11.     private void Awake()
    12.     {
    13.         if (TextObjects == null || TextObjects.Length == 0)
    14.             return;
    15.  
    16.         // Iterate over each of the text objects in the array to find a good test candidate
    17.         // There are different ways to figure out the best candidate
    18.         // Preferred width works fine for single line text objects
    19.         int candidateIndex = 0;
    20.         float maxPreferredWidth = 0;
    21.  
    22.         for (int i = 0; i < TextObjects.Length; i++)
    23.         {
    24.             float preferredWidth = TextObjects[i].preferredWidth;
    25.             if (preferredWidth > maxPreferredWidth)
    26.             {
    27.                 maxPreferredWidth = preferredWidth;
    28.                 candidateIndex = i;
    29.             }
    30.         }
    31.  
    32.         // Force an update of the candidate text object so we can retrieve its optimum point size.
    33.         TextObjects[candidateIndex].enableAutoSizing = true;
    34.         TextObjects[candidateIndex].ForceMeshUpdate();
    35.         float optimumPointSize = TextObjects[candidateIndex].fontSize;
    36.  
    37.         // Disable auto size on our test candidate
    38.         TextObjects[candidateIndex].enableAutoSizing = false;
    39.  
    40.         // Iterate over all other text objects to set the point size
    41.         for (int i = 0; i < TextObjects.Length; i++)
    42.             TextObjects[i].fontSize = optimumPointSize;
    43.     }
    44. }
    45.  
    Add the following scrip to an empty game object. Populate the public array with text objects that all have auto size disabled. These would be text objects that you expect to end up all at the same point size.

    Screen shot of the scene setup before entering playmode

    upload_2019-10-18_14-42-52.png

    Result when using just auto size on these objects

    upload_2019-10-18_14-43-57.png

    As you can see they end up with different point size which is weird from a design point of view.

    Now with auto size disabled and using the example script we get the following result.

    upload_2019-10-18_14-48-0.png

    In this case they are all using the same final point size and we only used auto size on one text object so better performance.

    Since auto size is typically used to handle resolution variance between devices which is something that needs to be addressed when loading a scene the first time, this also means we only have to do this once unless the text will be changing of course.
     
    Last edited: Oct 18, 2019
    skabed, wing3s, Zyke and 3 others like this.
  7. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Hello,

    Thank you for your detailed answer.
    I tried something similar and could get ~100ms from the initialization !
    I'll continue to optimize group of texts to reduce CPU consommation.

    Can you tell me, when the textMeshPro force an autosize update ?
    So that i might optimize further more by correctly understanding this.

    Is it another way to keep performance when the entire application is able to be translated ?
    The reason I have ALL my texts setted in autoSize is because I allow the user to do a hot translate.
    I have a solution to manually pass throught each TMP to revalidate size but is there another way to do it ?

    Best regards,

    Julien
     
  8. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    When Text Auto Size is enabled, any change to the text object's properties that affect layout (like the text for instance) will result in an update of the text and auto size.

    So whenever a user changes the language, you should re-run the equivalent of the process I outlined in my previous post.
     
    masterton likes this.
  9. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Ok, thank you for the information.
    I hope there will be some internal optimization for TMP.

    I could optimize like 200ms but I use TextMeshPro with autosize (refreshed manually) inside a canvas (mask).
    So during the animation, the TMP refresh non stop.

    I'll continue optimizing and wait a possible futur release.

    Thank you for this :)
     
  10. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Auto Size is an iterative process where multiple point size values have to be tested to determine the Optimum point size. The greater the auto size range the more potential iterations and resulting performance overhead. This is not something that can be much further optimized.

    Like I said before, you should always strive to minimize the use of Auto Size. Auto size should not be left enabled on text objects that are not static or will be changing.

    Like I said before, once you have determined the Optimum point size based on some screen resolution / language, you should not have to change the point size again. For instance, as you switch from English to German, you should find the new Optimum point size for the German text. Once you have this new Optimum point size, all text object groups should be using that Optimum point size with Auto size disabled.

    If you are animating any part of your UI that includes text objects, those text objects should have auto size disabled. There is no reason to have auto size remain enabled especially when animating those objects.
     
  11. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    I haven't looked at the code, maybe it's implemented that way already or maybe it makes no sense what I'm going to say, but perhaps some sort of binary-search like auto size algorithm would improve performance?
     
  12. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    It is implemented using binary search.

    Reducing the point size accuracy which is at 0.05 to something higher like 0.25 or 0.5 would reduce the number of iterations by a few but those gains will never be as significant as using this feature in a conservative way and only when it is needed.

    To quote Spider-Man, "With great power comes great responsibility" ... auto-size is a powerful feature and it must be used wisely and strategically.

    P.S. The above doesn't mean I won't keep looking for ways to potentially improve upon this feature but more importantly the overall performance of the parser and layout system which would yield better overall gains.
     
    Last edited: Oct 22, 2019
    Peter77 likes this.
  13. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Ok, thank you for the details.
    I guess I can keep in cache the evaluated size when I need a resize to search inside dictionnary.

    I got all explaination i needed :)
    TY !!!
     
  14. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    From my experience, if you target more than one language, pretty much every text ends up using auto-size.
     
    forestrf likes this.
  15. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Yes, and this is the problem !
    I have text taht need to be translated (at any moment) inside animation.
    I guess I'll keep in cache evaluated fontsize. I'll keep spike lag but only in the first access :)
     
  16. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    How is this text translated? Trying to understand the "at any moment" part.

    How is this text animated? I mean from a visual point of view.
     
  17. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    The user can clic a button to change language at any moment of the application. (I don't like that but it's required)

    There are so many type of animation, for example a box that move with text inside and the text is manipulated (size, alpha, ...). so it trigger a rebuild of textmeshpro
     
  18. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Is the translated text known at build time or is the text coming from user input or some external source where the text is not known ahead of time?

    Moving a text object should not require a rebuild of the text object.

    By size do you mean point size or scale of the text object?

    Scale changes should not trigger a rebuild of the text object. Point size changes will so it is best to animate scale and not point size.
     
  19. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    The translated text comes from and translation file with text content and sometimes it uses some unknown value like score, pseudo, location, ...

    For the animation, I guess we manipulate everything but I can say it because it's designer job.
    I'll speak with them to avoid changing point size so !
     
  20. Satochu

    Satochu

    Joined:
    Feb 4, 2015
    Posts:
    14
    Hello,

    I just up this post for a last question before next release.
    Can I Improve performance by reducing autosizing precision ?

    I saw the parameters in the settings : "Text auto size ratios".
     
  21. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Not at this time but I can take a look at adding support for that in the TMP Settings.
     
  22. easy_kyriakos

    easy_kyriakos

    Joined:
    Mar 13, 2020
    Posts:
    3
    @Stephan_B

    I have tried your TextAutoSizeController and it works as expected. However, if the text buttons to be re-sized are under a Vertical Layout Group, the optimumPointSize result is 0. I am using Unity 2019.3.0f6 and TMP 2.0.1.

    Before run:

    Screenshot 2020-09-03 at 12.10.41 PM.png


    After run:
    Screenshot 2020-09-03 at 12.11.09 PM.png
     
    Last edited: Sep 9, 2020
  23. easy_kyriakos

    easy_kyriakos

    Joined:
    Mar 13, 2020
    Posts:
    3
    @Stephan_B did u had any time to look at this issue? Need some help here )
     
  24. Peecha

    Peecha

    Joined:
    Dec 2, 2013
    Posts:
    23
  25. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Please use the latest release of the TMP package as I have addressed a similar issue with Preferred Values in it.

    The latest release is version 1.5.3 for Unity 2018.4, version 2.1.3 for Unity 2019.4 and version 3.0.3 for Unity 2020.x.

    Let me know if the issue persists in the latest release.
     
  26. CLSStudio

    CLSStudio

    Joined:
    Jun 11, 2015
    Posts:
    3
    I'm having the same problem. Since I can't update TMP at the moment, I looked for a workaround.
    Calling
    Code (CSharp):
    1. LayoutRebuilder.ForceRebuildLayoutImmediate( verticalLayout );
    before enabling the auto sizing on the candidate TMP_Text seems to do the trick (or at least it works in my scenario).

    EDIT: I also moved the computation from Awake to Start. It's still not perfect since in some cases it picks a size slightly bigger or slightly smaller than it should.
     
    Last edited: Nov 2, 2020
    Nihil688 likes this.
  27. Peecha

    Peecha

    Joined:
    Dec 2, 2013
    Posts:
    23
    I was on 2.1.1, updating to latest version didn't help. On first enable of the text items, the optimumPointSize reports 0. On second it gets some value, but it is way off the size it should be. On subsequent enable is again 0, etc.

    I am using slightly different implementation which is working for me:

    Code (CSharp):
    1.  
    2. public static void SetTextArrayUniformAutoSize(TMPro.TMP_Text[] textObjects, float maxFontSize)
    3. {
    4.     if (textObjects == null || textObjects.Length == 0)
    5.         return;
    6.  
    7.     textObjects.DoForEach(x => x.fontSizeMax = maxFontSize);
    8.     textObjects.DoForEach(x => x.ForceMeshUpdate(true));
    9.  
    10.     float newMaxSize = maxFontSize;
    11.     for (int i = 0; i < textObjects.Length; i++)
    12.     {
    13.         float minSize = textObjects[i].fontSize;
    14.         if (minSize < newMaxSize)
    15.         {
    16.             newMaxSize = minSize;
    17.         }
    18.     }
    19.  
    20.     for (int i = 0; i < textObjects.Length; i++)
    21.     {
    22.         textObjects[i].fontSizeMax = newMaxSize;
    23.         textObjects[i].ForceMeshUpdate(true);
    24.     }
    25. }
    This would still fail if the text rect is set to vertical stretch. Luckily i didn't need the stretching and have it set to specific height.
     
  28. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    253
    Hello everyone, here is my a bit rework version of script by Stephan:

    GitHub

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using TMPro;
    6.  
    7. namespace Plugins.mitaywalle.UI.Layout
    8. {
    9.     public enum eResizePattern
    10.     {
    11.         IgnoreRichText,
    12.         AllCharacters,
    13.     }
    14.  
    15.     [ExecuteAlways]
    16.     public class TMPTextAutoSize : MonoBehaviour
    17.     {
    18.         [SerializeField] private List<TMP_Text> _labels = new List<TMP_Text>();
    19.         [SerializeField] private eResizePattern _pattern;
    20.         [SerializeField] private bool _executeOnUpdate;
    21.         private int _currentIndex;
    22.  
    23.         private void Update()
    24.         {
    25.             if (_executeOnUpdate) Execute();
    26.  
    27.             OnUpdateCheck();
    28.         }
    29.  
    30.         public void Execute()
    31.         {
    32.             if (_labels.Count == 0) return;
    33.  
    34.             int count = _labels.Count;
    35.  
    36.             int index = 0;
    37.             float maxLength = 0;
    38.  
    39.             for (int i = 0; i < count; i++)
    40.             {
    41.                 float length = 0;
    42.  
    43.                 switch (_pattern)
    44.                 {
    45.                     case eResizePattern.IgnoreRichText:
    46.                         length = _labels[i].GetParsedText().Length;
    47.  
    48.                         break;
    49.  
    50.                     case eResizePattern.AllCharacters:
    51.                         length = _labels[i].text.Length;
    52.  
    53.                         break;
    54.  
    55.                     default:
    56.                         throw new ArgumentOutOfRangeException();
    57.                 }
    58.  
    59.                 if (length > maxLength)
    60.                 {
    61.                     maxLength = length;
    62.                     index = i;
    63.                 }
    64.             }
    65.  
    66.             if (_currentIndex != index)
    67.             {
    68.                 OnChanged(index);
    69.             }
    70.         }
    71.         private void OnChanged(int index)
    72.         {
    73.             // Disable auto size on previous
    74.             _labels[_currentIndex].enableAutoSizing = false;
    75.  
    76.             _currentIndex = index;
    77.  
    78.             // Force an update of the candidate text object so we can retrieve its optimum point size.
    79.             _labels[index].enableAutoSizing = true;
    80.             _labels[index].ForceMeshUpdate();
    81.         }
    82.         private void OnUpdateCheck()
    83.         {
    84.             float optimumPointSize = _labels[_currentIndex].fontSize;
    85.  
    86.             // Iterate over all other text objects to set the point size
    87.             int count = _labels.Count;
    88.  
    89.             for (int i = 0; i < count; i++)
    90.             {
    91.                 if (_currentIndex == i) continue;
    92.  
    93.                 _labels[i].enableAutoSizing = false;
    94.  
    95.                 _labels[i].fontSize = optimumPointSize;
    96.             }
    97.         }
    98.     }
    99. }
    100.  

    TMPTextAutoSize_example.gif
     
    skabed, Nit_Ram and Zyke like this.
  29. Alexander_V

    Alexander_V

    Joined:
    Feb 26, 2020
    Posts:
    8
    Hey @Stephan_B , I have an additional condition, and I can't wrap my head around it. Imagine you have a bulleted list as multiple TMP labels in a VerticalLayoutGroup which has a fixed height and width. I need somehow to make the text size of each label uniform so they all could fit into the bounding box or take less space.
    All examples above show just fixed-height containers, while I have only fixed width. The height of each label should be automatically set to a proper value, and all labels should have the same text size so they all can fit into the parent bulleted list container.
    Maybe you could nudge me into the right direction?
    Thank you!
     
    Last edited: Mar 22, 2023
  30. Nihil688

    Nihil688

    Joined:
    Mar 12, 2013
    Posts:
    503
    The issue does persist in 3.0.6