Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

TextMesh Pro Garbage when changing outlineColor

Discussion in 'UGUI & TextMesh Pro' started by Robdon, Mar 3, 2018.

  1. Robdon

    Robdon

    Joined:
    Jan 10, 2014
    Posts:
    141
    I seem to be getting a little bit of garbage when I modify outlineColor.

    I am 'pulsating' some of my text, and in update() I'm lerping .color, .fontSize and .outlineColor like this:


    TheText.fontSize = Mathf.SmoothStep(fromSize, toSize, t);
    TheText.color = Color32.Lerp(fromTextColor, toTextColor, t);
    TheText.outlineColor = Color32.Lerp(fromTextColor, toTextColor, t);


    The size and color don't generate any garbage, but the outlineColor does seem to, about 470 bytes per frame.

    It looks like its the calls to the ShaderUtilities that's doing it.

    I can't see any way around it for me, but wondered if this could be modified, so that no garbage is created?

    Here is the profiler:
    TextMeshProGarbage.png
     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Text object properties like outlineColor, faceColor, etc. which modify a material property, result in the creation of a material instance. This process would generate some allocations.

    It is important to think about whether or not you are trying to change the material property of a single text object or the material properties of all text objects to which this material is assigned. If per individual text object, make sure those instances are getting re-used and not creating new one every time.

    These properties (outlineColor, etc.) are just provided for convenience as in the end, they end up modifying the material properties. So the same result could be achieved by using using the material API and to change these directly. There are several posts on the TextMesh Pro user forum where you can get additional information. Here is one such post

    Next, since the ShaderUtilities class gathers the propertyID of the material, the first time it gathers those, there will be some allocations but this should only occur once. The alternative is to access those material properties by name which uses a string which in the end would result in allocation every single time, we change the material property.

    If you are getting GC every single frame, then please provide me with whatever script you are using to make these changes and I can look to make sure nothing funny is going on that should not be happening (ie. generating GC where it should not).
     
  3. Robdon

    Robdon

    Joined:
    Jan 10, 2014
    Posts:
    141
    As far as I can see then, this script should not be getting GC then?

    It's a very simple test, I just have this script on a TextMeshPro UGUI object, but it seems to create garbage every frame.

    If I comment out the 'outlineColor' line, then the GC doesn't happen.

    BTW, I'm on Unity 2017.3.0f3 and the latest TMP from the asset store.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using TMPro;
    4.  
    5. public class test : MonoBehaviour {
    6.  
    7.     float t;
    8.     int direction = 1;
    9.     private float fromSize;
    10.     private float toSize;
    11.  
    12.     private Color32 startTextColor;
    13.     private Color32 endTextColor;
    14.  
    15.     private Color32 fromTextColor;
    16.     private Color32 toTextColor;
    17.  
    18.     void Start () {
    19.         GetComponent<TextMeshProUGUI>().outlineWidth = 0.2f;
    20.         t = 1;
    21.     }
    22.  
    23.     void Update () {
    24.         if (t >= 1.0) {
    25.             direction *= -1;
    26.  
    27.             if (direction == 1) {
    28.                 fromSize = 36;
    29.                 toSize = 46;
    30.                 fromTextColor = new Color32(255,0,0,255);
    31.                 toTextColor = new Color32(255,255,0,255);
    32.             }
    33.             else {
    34.                 fromSize = 46;
    35.                 toSize = 36;
    36.                 fromTextColor = new Color32(255, 255, 0, 255);
    37.                 toTextColor = new Color32(255, 0, 0, 255);
    38.             }
    39.  
    40.             t = 0;
    41.         }
    42.  
    43.         t += (Time.deltaTime * 1.5f);
    44.  
    45.         GetComponent<TextMeshProUGUI>().fontSize = Mathf.SmoothStep(fromSize, toSize, t);
    46.         GetComponent<TextMeshProUGUI>().color = Color32.Lerp(fromTextColor, toTextColor, t);
    47.         GetComponent<TextMeshProUGUI>().outlineColor = Color32.Lerp(fromTextColor, toTextColor, t);
    48.  
    49.     }
    50. }
    51.  
    52.  
     
    Last edited by a moderator: Mar 3, 2018
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Thank you for posting the script. I should be able to look into this later today.
     
    Robdon likes this.
  5. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I had a chance to look into this and the GC is the result of checking / using the material.shaderKeywords material property which given this function returns a string results in GC.

    Having said, here is a revised script / implementation which is allocation free. Note that we still get an initial allocation when the material instance and the outline width is initially set but this only occurs once in Start(). The key is having zero allocations in Update().

    I have added comments in the code to explain all of this stuff :)

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using TMPro;
    4. public class TextOutlineUpdater : MonoBehaviour
    5. {
    6.     float t;
    7.     int direction = 1;
    8.     private float fromSize;
    9.     private float toSize;
    10.     private Color32 startTextColor;
    11.     private Color32 endTextColor;
    12.     private Color32 fromTextColor;
    13.     private Color32 toTextColor;
    14.     private TMP_Text m_TextComponent;
    15.     private Material m_TextMaterialInstance;
    16.     void Awake()
    17.     {
    18.         // Caching a reference to the Text Component for efficiency. Using GetComponent<TypeOf> in an update loop is not advisable for performance reasons.
    19.         m_TextComponent = GetComponent<TMP_Text>();
    20.         // Accessing the material will return an instance of the material used by this assigned font asset. This instance will also be automatically assigned to the text object.
    21.         m_TextMaterialInstance = m_TextComponent.fontMaterial;
    22.     }
    23.     void Start()
    24.     {
    25.         // Set outline width directly on the new material instance.
    26.         m_TextMaterialInstance.SetFloat(ShaderUtilities.ID_OutlineWidth, 0.2f);
    27.  
    28.         // Since the padding around the geometry of the text needs to be adjusted to account for changes in properties like Outline Thickness, Underlay Offsets, Glow, etc.
    29.         // We need to force a check / update of this padding.
    30.         // In this case since the padding will be constant, we only need to do this once. If the Outline Thickness was to keep on changing, then we would want to set the Outline Thickness to the maximum value and then call this UpdateMeshPadding function. Once set to the maximum value, any smaller thickness would not be an issue / result in clipping and although the mesh padding would be a bit too large, it would not really have any performance impact.
    31.         m_TextComponent.UpdateMeshPadding();
    32.  
    33.         // Note that I could have use m_TextComponent.outlineWidth = 0.2f; which would have created the instance of the material and set the Outline width.
    34.         // This would have also called the internal UpdateMeshPadding() function. However, I wanted to show an alternative way which helps to understand what is actually going on internally.
    35.  
    36.         t = 1;
    37.     }
    38.     void Update()
    39.     {
    40.         if (t >= 1.0)
    41.         {
    42.             direction *= -1;
    43.             if (direction == 1)
    44.             {
    45.                 fromSize = 36;
    46.                 toSize = 46;
    47.                 fromTextColor = new Color32(255, 0, 0, 255);
    48.                 toTextColor = new Color32(255, 255, 0, 255);
    49.             }
    50.             else
    51.             {
    52.                 fromSize = 46;
    53.                 toSize = 36;
    54.                 fromTextColor = new Color32(255, 255, 0, 255);
    55.                 toTextColor = new Color32(255, 0, 0, 255);
    56.             }
    57.             t = 0;
    58.         }
    59.         t += (Time.deltaTime * 1.5f);
    60.         m_TextComponent.fontSize = Mathf.SmoothStep(fromSize, toSize, t);
    61.  
    62.         // Here again, we access the material directly which avoids TMP calling the internal functions to update the mesh padding which in turn have to check ShaderKeywords which is what is causing the GC.
    63.         m_TextMaterialInstance.SetColor(ShaderUtilities.ID_OutlineColor, Color32.Lerp(fromTextColor, toTextColor, t));
    64.     }
    65. }
    66.  
    upload_2018-3-3_16-1-42.png
     
    Last edited: Mar 4, 2018
    Robdon likes this.
  6. Robdon

    Robdon

    Joined:
    Jan 10, 2014
    Posts:
    141
    Perfect, no GC when I test it my side either.

    Thanks for that Stephan, and the explanations in the comments. :)
     
    Stephan_B likes this.