Optimizing Unity UI(五):Optimizing UI Controls

Version check: 2017.3 -Difficulty: Advanced

This section of the Optimizing UnityUI guide focuses on issues specific to certain types of UI controls. Although most UI controls are relatively similar in terms of performance, both are the cause of many performance issues encountered in games that are close to shippable.

UI text

Unity's built-in text component is a convenient way to display rasterized text symbols in the UI. However, there are many behaviors that are uncommon, but often appear as performance hot spots. When adding text to the UI, always remember that the text symbol is actually presented in the form of a single quad, one for each character. These quads often have a lot of space around the glyph, depending on its shape, and it is easy to position the text in such a way that it inadvertently breaks the batching of other UI elements.

Text mesh rebuilds

A major problem is the reconstruction of the UI text grid. Whenever the UI text component is changed, the text component must recalculate the polygon used to display the actual text. If you only disable and re-enable any of the text component or its parent game object, this recalculation will also occur without changing the text.

This behavior is problematic for any UI that displays a large number of text labels, most commonly leaderboards or statistics screens. Since the most common way to hide and display the unified UI is to enable/disable the GameObject that contains the UI, UIs with a large number of text components usually cause undesirable frame rate issues when displayed.

For a potential solution to this problem, see the Disabling Canvases section in the next chapter .

Dynamic fonts and font atlases

When the complete set of displayable characters is very large or unknown at runtime, dynamic fonts are a convenient way to display text. In Unity's implementation, these fonts construct a glyph Atlas at runtime based on the characters encountered in the UI text component.

Each different font object loaded will maintain its own Texture Atlas, even if it is another font in the same font family. For example, using Arial with bold text on one control and ArialBold on another control will produce the same output, but Unity will maintain two different texture atlases-one for Arial and the other for In ArialBold.

From a performance point of view, the most important thing is that UnityUI’s dynamic fonts reserve a glyph for each different size, style, and character combination in the font texture atlas. In other words, if the UI contains two text components, and both components display the letter "A", then:

  • If two text components are the same size, a glyph will be included in the font atlas.
  • If two text components do not share the same size (for example, one is 16 points and the other is 24 points), the font atlas will contain two copies of the letter "A" in different sizes.

  • If one text component is bold and the other is not, then the font map will contain a bold'A' and a normal'A'.

Whenever a UI text object with a dynamic font encounters a glyph that has not been rasterized into the texture map of the font, the texture map of the font must be reconstructed. If the new glyph is suitable for the current atlas, it is added and the atlas is re-uploaded to the graphics device. However, if the current atlas is too small, the system tries to rebuild atlas. It does this in two stages.

First, atlas was rebuilt to the same size, using only the symbols currently displayed by the Active UI text component. Includes UI text components that have the parent canvas enabled but the canvas renderer has been disabled. If the system installs all the symbols currently in use into a new atlas, it will rasterize the atlas instead of continuing to the second step.

Second, if the atlas currently in use cannot be the same size as the current atlas, a larger atlas can be created by doubling the shorter size of the atlas. For example, 512x512atlas expands to 512x1024atlas.

Due to the above algorithm, the atlas of the dynamic font will only grow after it is created once. Considering the cost of reconstructing texture atlases, it must be minimized during the reconstruction process. This can be done in two ways.

Use non-dynamic fonts whenever possible, and provide pre-configuration support for the required glyph set. This usually applies to UIs that use a well-constrained character set, such as only Latin/ASCII characters and a small size range.

If a very large range of characters must be supported, such as the entire Unicode set, the font must be set to Dynamic. To avoid predictable performance problems, set the font's glyph atlas to a set of appropriate characters Font.RequestCharactersInTexture at startup .

Please note that font atlas reconstruction is triggered individually for each UI text component to be changed. When filling a large number of text components, you can collect all the unique characters in the content of the text component and use the font atlas as the home page. This will ensure that the glyph atlas only needs to be rebuilt once, rather than every time a new glyph is encountered.

Please also note that when the font atlas rebuild is triggered, any characters not currently included in the active UI text component will not appear in the new atlas, even if these characters were originally added to the atlas due to the call to Font.RequestCharactersInTexture . . To resolve this limitation, please subscribe to Font.textureRebuilt delegate and query Font.characterInfo to ensure that all required characters remain ready.

The Font.textureRebuild delegate is currently undocumented. This is an argument UnityEvent. The parameter is the font whose texture is reconstructed. Subscribers to this event should follow the following signature:

public void TextureRebuiltCallback(Font rebuiltFont) { /* ... */ }

Specialized glyph renderers

In the case that hieroglyphs are well-known, there is a relatively fixed position between each glyph, and it is very advantageous to write a custom component to display the sprites of these symbols. An example of this might be a score display.

For scores, the displayable characters are drawn from a known set of glyphs (numbers 0-9), do not change everywhere, and appear at a fixed distance. It is relatively simple to decompose an integer into its number and display the appropriate number flashing. This specialized digital display system can be constructed in a way that is both assignable and faster to calculate, animate, and display than canvas-driven UI text components.

Fallback fonts and memory usage

For applications that must support large character sets, it is tempting to list a large number of fonts in the "font name" field of the font importer. If the glyph cannot be in the main font, any font listed in the "Font Name" field will be used as a fallback. The fallback order is determined by the order in which the fonts are listed in the "Font Name" field.

However, to support this behavior, Unity will keep all fonts listed in the "font name" loaded into memory. If the character set of the font is very large, the amount of memory consumed by the fallback font may be excessive. This is often seen when including pictographic fonts (such as Kanji or Kanji).

Best Fit and performance

In general, the UI Text component's Best Fit setting should never be used.

"BestFit" dynamically adjusts the size of the font to the maximum integer point size, which can be displayed in the bounding box of the text component without overflow, clamped to the configurable minimum/maximum point size. However, because Unity will present different glyphs in font atlases, using "BestFit" will quickly cover atlases with many different font sizes.

Starting from Unity 2017.3, the size detection used by the best match is non-optimal. It generates glyphs in the font atlas for each size increment tested, which further increases the time required to generate the font atlas. It will also cause the atlas to overflow, causing old graphics to be kicked out of the atlas. Due to the extensive testing required for the best match calculation, this usually eliminates glyphs that are being used by other text components and forces the font atlas to be regenerated at least once after calculating the appropriate font size. This particular problem has been corrected in Unity 5.4, and the "best match" does not unnecessarily expand the texture atlas of the font, but is still much slower than static-sized text.

Frequent font atlas reconstruction will quickly reduce runtime performance and cause memory fragmentation. The greater the number of text components set as the best match, the more serious the problem.

TextMeshPro Text

TextMesh Pro (TMP) is a replacement for Unity's existing text components (such as text grid and UI text). TextMesh Pro (SDF) serves as its main text rendering pipeline, so that text can be clearly presented at any point, size and resolution. TextMesh Pro uses a set of custom shaders to take advantage of the features of SDF text rendering, by simply changing the material properties to add visual styles such as expansion, contours, soft shadows, bevels, textures, glow, etc., and by creating/using material presets To save and recall these visual styles, thereby dynamically changing the visual appearance of the text.

Before the release of 2018.1, TextMesh Pro was included in one's project as an asset store. Starting from 2018.1, TextMesh Pro will serve as the Package Manager package.

Text mesh rebuilds

Just like Unity's built-in UIText component, making changes to the text displayed by the component will trigger calls to Canvas.endWillRendererCanvases and Canvas.BuildBatch, which can be expensive. Try to minimize the changes to the text field of the TextMeshProUGUI component, and ensure that the text of the parent component TextMeshProUGUI component is frequently changed to the parent GameObject with its own canvas component to ensure that the canvas regeneration call remains as efficient as possible.

Please note that for text displayed in world space, we recommend that users use normal TextMeshPro components instead of using TextMeshProGUI as the canvas in WorldSpace, which may be inefficient. If there is no canvas system overhead, using TextMeshPro will be more effective.

Fonts and memory usage

Since there is no dynamic font function in TMP, you must rely on fallback fonts. When using TMP, understanding how to load and use fallback fonts is essential to optimize memory.

Glyph discovery in TMP is done recursively-that is, when a glyph is missing from TMP font assets, TMP iterates the list of currently assigned or active fallback font assets, starting from the first fallback on the list, and passing itself The fallback. If the glyph is still not found, TMP will search for any Sprite assets that may be assigned to the text object, as well as any fallbacks assigned to this Sprite asset. If the required glyph is still not found, TMP will recursively search the regular fallback list allocated in the TMP settings file, followed by the default Sprite Asset. If it still cannot find this glyph, it will search for the default font asset assigned in the TMP settings. As a last resort, TMP will use and display missing glyph replacement characters defined in the TMP settings file.

The font assets of TextMesh Pro are loaded when referenced in a scene or project. They are mainly referenced by TextMeshPro text components, TMP settings, and font assets themselves as backup fonts. If Font assets are referenced in the TMP setting assets, when the first scene with the TMP text component is activated, these font assets and all fallback font assets will be recursively loaded. If the default Sprite sheet asset is referenced, it will also be loaded.

In addition, when a font resource is referenced by a TextMeshPro component in a given scene and has not been loaded through TMP settings, once the component is activated, the referenced font resource and all of its fallback font assets will be recursively loaded. When working on a project with many fonts, especially if available memory is an issue, it is important to remember this process.

For the above reasons, localization projects become a concern when using TMP, because all localized language font assets loaded through TMP settings will be harmful to memory pressure. If localization is a necessary requirement, we recommend the potential strategy of loading font assets in a modular manner when necessary (such as loading various scenes) or using Asset Bundles.

When the application starts, it should include a guided step to verify the user's locale and set the font asset fallback for each font asset:

  1. Create Asset Bundles for the basic TMP font assets (for example, the smallest Latin character for each font)
  2. Create an Asset Bundle for the backup TMP font assets required for each language (for example, create an Asset Bundle for TMP fonts for each font required for Japanese)
  3. Load the basic Asset Bundle in the boot step
  4. According to the locale, use the fallback font to load the required Asset Bundle package

  5. For each font in the basic Asset Bundle, assign a fallback font asset from the localized font Asset Bundle
  6. Continue to guide the game

If you do not use images, you can also delete the default Sprite asset reference from the TMP settings to save additional memory.

Best Fit and performance

Again, if TextMeshPro does not have dynamic font capabilities, the problems outlined in the UGUIUIText section on best fit above will not occur. When using "Best Fit" on the TextMeshPro component, the only thing to consider is to use binary search to find the correct size. When using text auto-sizing, it is best to test the optimal point size for the longest/largest text block. After determining this optimal size, disable automatic resizing of a given text object, and then manually set this optimal point size on other text objects. This helps to improve performance and avoid using a set of text objects with different dot sizes, which are considered bad visual/printing practices.

Scroll Views

After the fill rate issue, UnityUI's Scroll view is the second most common source of runtime performance issues. A scroll view usually requires a large number of UI elements to represent its content. There are two basic ways to fill the scroll view:

  • Fill it with all the elements needed to represent the contents of all scroll views

  • Collect elements and reposition them as needed to represent visible content.

Both solutions have problems.

The first solution takes longer and longer to instantiate all UI elements, because the number of items to be represented has increased, and the time required to rebuild the scroll view needs to be increased. If only a few elements are needed in the scroll view, for example, only a few text components need to be displayed in the scroll view, then the simplicity of this method is even better.

The second solution requires a lot of code to be implemented correctly under the current UI and layout system. Two possible methods are discussed in further detail below. For any significantly complex scrolling UI, some type of pooling method is usually needed to avoid performance issues.

Despite these problems, all methods can be improved by adding rectangular 2D components to the Scroll View. This component ensures that scroll view elements outside the Scroll View viewport are not included in the list of drawable elements. These elements must be generated, sorted, and analyzed when the canvas is rebuilt.

Simple Scroll View element pooling

The easiest way to implement an object pool with a scroll view, while still retaining the native convenience of using Unity's built-in scroll view component, is to use a hybrid approach:

To lay out elements in the UI, which will allow the layout system to correctly calculate the size of the scroll view content and allow the scroll bars to work properly, use GameObjects with Layout Element components as "placeholders" for visible UI elements.

Then, instantiate a pool of visible UI elements that is enough to fill the visible part of the visible area of ​​the Scroll View, and place these elements in the positioning placeholders. When scrolling the view, reuse UI elements to display the content that has been scrolled into the view.

This will greatly reduce the number of UI elements that must be batched, because the cost of batching is only increased based on the number of canvas renderers in the canvas rather than the number of RECT conversions.

Problems with the simple approach

Currently, whenever any UI element is reloaded or the order of its siblings is changed, the element and all its child elements are marked as "dirty" and its canvas is forced to rebuild.

The reason for this is that Unity does not separate the callbacks that fix the conversion and change the order of their sibling relationships. Both of these events will trigger the OnTransformParentChanged callback. In the source code of UnityUI's graphics class (see Graphic.cs in the source code), the callback is implemented and the SetAllDirty method is called. By dirtying the Graphic, the system ensures that Graphic will rebuild its layout and vertices before the next frame is rendered.

You can assign the canvas to the root RectTransform of each element in the "Scroll View", and then this element will only limit the reconstruction to the repaired elements, rather than the entire contents of the "Scroll View". However, this increases the number of draw calls required to render the Scroll View. In addition, if a single element in the Scroll View is complex and contains more than a dozen graphical components, especially if there are a large number of layout components on each element, the cost of rebuilding them is usually high enough to significantly reduce the cost on low-end devices. Frame rate.

If the Scroll View UI elements do not have variable sizes, it is unnecessary to completely recalculate the layout and vertices. However, to avoid this behavior, you need to implement an object pool solution based on location changes, rather than parent or sibling changes.

Position-based Scroll View pools

In order to avoid the above problems, you can create a Scroll View object pooling its object by simply moving the RectTransform of the contained UI element. This avoids the need to rebuild its content without changing the dimensions of the moved RectTransform, thereby significantly improving the performance of the Scroll View.

To achieve this, it is usually best to either write a custom scroll view subclass or a custom layout component. The latter is usually a simpler solution and can be implemented by implementing a subclass of UnityUI's LayoutGroup abstract base class.

The custom layout group can analyze the underlying source data to check how many data elements must be displayed, and can appropriately adjust the size of the ContentRectTransform of the Scroll View. It can then subscribe to Scroll View change events and use these events to reposition its visible elements accordingly.

Guess you like

Origin blog.csdn.net/Momo_Da/article/details/93617737