Optimizing Unity UI(四):Fill-rate, Canvases and input

Version check: 2017.3 -Difficulty: Advanced

This chapter discusses the broader issues of building UnityUI.

Remediating fill-rate issues

To reduce the pressure on the GPU fragmentation pipeline, two actions can be taken:

  • Reduce the complexity of the fragment shader. For more information, please refer to the section "UI shaders and low-spec devices".
  • Reduce the number of pixels that must be sampled.

Since UI shaders are usually standardized, the most common problem is excessive use of fill rate. The most common reason for this is a large number of overlapping UI elements and/or multiple UI elements occupying an important part of the screen. Both of these issues can lead to extremely high levels of overdraft.

In order to reduce overuse of fill rate and reduce overdraft, please consider the following possible remedies.

Eliminating invisible UI

The least need to redesign existing UI elements is to simply disable elements that are not visible to the player. The most common case for this is to open a full-screen UI with an opaque background. In this case, any UI element placed under the full screen UI can be disabled.

The easiest way is to disable the root GameObject or GameObjects that contain invisible UI elements. See the Disabling Canvases section for alternative solutions .

Finally, by setting the alpha of the UI element to 0, make sure that the UI element is not hidden, because the element will still be sent to the GPU and may cost valuable rendering time. If the UI element does not require a graphical component, you can simply delete it and the ray casting will still work.

Simplify UI structure

In order to reduce the time required to reconstruct and present the UI, the number of UI objects must be reduced as much as possible. Bake as much as possible. For example, don't just use a mixed GameObject to change the color tone to an element, but instead achieve this through material properties. Also, don't create game objects like folders, which have no other purpose other than organizing your scene.

Disabling invisible camera output

If a full-screen user interface with an opaque background is opened, the world space camera will still present the standard 3D scene behind the UI. The renderer does not know that the full screen Unity user interface will blur the entire 3D scene.

Therefore, if a completely full screen user interface is turned on, disabling any and all blurry world space cameras will help reduce the pressure on the GPU, and simply eliminate useless work to render the 3D world.

If the UI does not cover the entire 3D scene, you may want to render the scene once and use it instead of constantly presenting it. You will lose the possibility of seeing animated content in a 3D scene, but this should be acceptable in most cases.

Note: If the canvas is set to " Screen Space-Overlay ", then it will be drawn regardless of the number of active cameras in the scene.

Majority-obscured cameras

Many "full-screen" UIs don't actually cover the entire 3D world, but make a small part of the world visible. In these cases, it may be more ideal to capture only the part of the world that is visible in the rendered texture. If the visible part of the world is "cached" in the rendering texture, the actual world space camera can be disabled, and the cached rendering texture can be drawn behind the UI screen to provide an illustrative version of the 3D world.

Composition-based UIs

It is very common for designers to create UI by combining and layering standard backgrounds and elements to create the final UI. Although this is relatively simple and very friendly to iteration, because UnityUI uses a transparent rendering queue, this is not in line with performance.

Consider a simple UI with a background, a button and some text on the button. Since the objects in the transparency queue are sorted from back to front, when the pixels fall into the text glyph, the GPU must sample the texture of the background, then the texture of the button, and finally the texture of the text atlas. A total of three Samples. As the complexity of the UI increases and more decorative elements are layered on the background, the number of samples will increase rapidly.

If you find that a large UI is bound by fill rate, the best way is to create a dedicated UI sprite and incorporate many decorative/unchanging elements of the UI into its background texture. This reduces the number of elements that must be layered on top of each other to achieve the desired design, but it is labor-intensive and increases the size of the project's texture atlas.

The principle of condensing the number of layered elements required to create a given UI to dedicated UI sprites can also be used for child elements. Consider a store UI with a product scroll pane. Each product UI element has a border, a background and some icons to indicate the price, name and other information.

The storage UI will need a background, but because its product is scrolling in the background, product elements cannot be merged into the background texture of the storage UI. However, the border, price, name, and other elements of the product UI elements can be incorporated into the product background. Depending on the size and number of icons, the fill rate savings can be considerable.

There are several disadvantages to combining layered elements. Professional elements can no longer be reused and need additional artist resources to create. Adding large new textures may significantly increase the amount of memory required to accommodate UI textures, especially if UI textures are not loaded and unloaded on demand.

UI shaders and low-spec devices

The built-in shaders used in the Unity user interface include support for masking, cropping, and many other complex operations. Due to this increased complexity, the performance of UI shaders is not as good as the simple Unity2D shaders on low-end devices such as iPhone 4.

If the application for low-end devices does not require masking, cropping, and other "fancy" features, you can create a custom shader to omit unused operations, such as this minimal UI shader:

Shader "UI/Fast-Default"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"
            
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
            };
            
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.worldPosition = IN.vertex;
                OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.worldPosition);

                OUT.texcoord = IN.texcoord;
                
                #ifdef UNITY_HALF_TEXEL_OFFSET
                OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
                #endif
                
                OUT.color = IN.color * _Color;
                return OUT;
            }

            sampler2D _MainTex;
            fixed4 frag(v2f IN) : SV_Target
            {
                return (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
            }
        ENDCG
        }
    }
}

UI Canvas rebuilds

To display any UI, the UI system must construct geometry for each UI component represented on the screen. This includes running dynamic layout code, generating polygons to represent characters in the UI text string, and merging as many geometric figures as possible into a single In the grid, to minimize draw calls. This is a multi-step process and is described in detail at the beginning of this guide.

Canvas reconstruction can become a performance issue due to two main reasons:

  • If the number of drawable UI elements on the canvas is large, the computational batch processing itself becomes very expensive. This is because the cost of sorting and analyzing elements has a linear relationship with the number of UI elements that can be drawn on the canvas.
  • If the canvas is often soiled, it may take too much time to refresh the canvas with relatively few changes.

As the number of elements on the canvas increases, both of these problems become acute.

Important note: When any drawable UI element on a given canvas changes, the canvas must rerun the batch build process. This process re-analyzes every drawable UI element on the canvas, regardless of whether it has changed. Please note that "change" refers to any changes that affect the appearance of UI objects, including the sprite assigned to the sprite renderer, the transition position and scale, the text contained in the text grid, etc.

Child order

UnityUI is constructed from back to front, and the order of objects in the hierarchy determines their sort order. The earlier objects in the hierarchy are considered to be the objects behind the objects. Batches are constructed by going from top to bottom hierarchy and collecting all objects that use the same material, the same texture, and no intermediate layer. The "middle layer" is a graphic object with different materials. Its bounding box overlaps two other hitable objects and is placed in the hierarchy between the two hitable objects. The middle layer forces the batch to break.

As discussed in the UnityUI analysis tools section, UI analyzers and framework debuggers can be used to inspect the middle layer of the UI. In this case, one drawable object will interact with two other drawable objects, otherwise these objects will become fragile.

The most common case of this problem is that the text and the sprite are close to each other: the bounding box of the text can overlap invisibly in the nearby SPTER, because the polygon of the text glyph is largely transparent. This problem can be solved in two ways:

  • Reorder the drawable objects so that the clickable objects are not inserted by non-clickable objects; that is, move the non-clickable objects above or below the clickable objects.
  • Adjust the position of the object to eliminate invisible overlapping space.

Both operations can be performed in UnityEditor, and UnityFrameDebugger can also be opened and enabled. By simply observing the number of draw calls visible in UnityFrameworkDebugger, an order and location can be found to minimize the number of wasted draw calls due to overlapping UI elements.

Splitting Canvases

Except for the simplest cases, in all cases, it is usually a good idea to separate a canvas, either by moving the element to a sub-canvas or moving the element to the same canvas.

Brother Canvas is best used in situations where certain parts of the UI must be separated from other parts of the UI to control its drawing depth so that it is always above or below other layers (such as tutorial arrows).

In most other cases, child canvases are more convenient because they inherit their display settings from their parent canvas.

Although at first glance, subdividing the UI into multiple sub-canvases is the best practice, remember that the canvas system also does not merge batches on different canvases. Performant UI design needs to strike a balance between minimizing reconstruction costs and minimizing wasteful drawing calls.

General guidelines

Because a canvas is re-batch when any of its constituent drawable components change, it is usually best to divide any non-trivial canvas into at least two parts. In addition, if you expect elements to change at the same time, it is best to try to co-locate the elements on the same canvas. For example, a progress bar and a countdown timer. They all depend on the same underlying data and therefore need to be updated at the same time, so they should be placed on the same canvas.

On a canvas, place all static and unchanging elements, such as backgrounds and labels. These will be batched once, when the canvas is displayed for the first time, then there will be no need to re-batch afterwards.

On the second canvas, place all the "dynamic" elements-those that change frequently. This will ensure that this canvas is mostly dirty. If the number of dynamic elements grows very large, it may be necessary to further subdivide the dynamic elements into a set of constantly changing elements (such as progress bar, timer readout, any animation) and a set of elements that only change occasionally.

In fact, this is quite difficult in practice, especially when packaging UI controls into prefabs. Many UIs choose to subdivide the canvas, splitting the more expensive controls into sub-canvases.

Unity 5.2 and Optimized Batching

In Unity 5.2, the batch code has been rewritten a lot, and the performance is much better than Unity 4.6, 5.0 and 5.1. In addition, on devices with multiple cores, the UnityUI system moves most of the processing to worker threads. Generally speaking, Unity 5.2 reduces the need to divide the UI into dozens of sub-canvases. Many UIs on mobile devices can now be performed with just two or three canvases.

For more information on optimizations in Unity 5.2, see this blog post .

Input and raycasting in Unity UI

By default, UnityUI uses the Graphic Raycaster component to handle input events, such as touch events and pointer hovering events. This is usually handled by the independent input manager component. Despite the name, independent input manager means a "universal" input manager system and will handle pointers and touches.

Erroneous mouse input detection on mobile (5.3)

Before Unity 5.4, each active Canvas and a Graphic Raycaster will run a raycast every frame to check the position of the pointer, as long as there is currently no touch input available. Regardless of the platform, this happens on iOS and Android devices; iOS and Android devices without a mouse will still query the position of the mouse and try to find which UI element is under that position to determine whether any hover events need to be sent.

This is a waste of CPU time, and has seen 5% or more of the CPU frame time using Unity applications.

This issue is resolved in Unity 5.4. Starting from 5.4, devices without a mouse will not query the mouse position and will not perform unnecessary ray casting.

If you are using a version older than 5.4, it is strongly recommended that mobile developers create their own input manager class. This can be as simple as copying the UnitedInputManager's standard input manager from the UnityUI source and commenting out the ProcessMouseEvent method and all calls to this method.

Raycast optimization

Graphic Raycaster is a relatively simple implementation that iterates all graphic components that have "Raycast Target" set to true. For each Raycast target, Raycaster performs a set of tests. If a Raycast target passes all its tests, it will be added to the hit list.

Raycast implementation details

The tests are:

  • If the Raycast target is active, enable and draw (ie has geometry)
  • If the input point is in the RectTransform connected to the Raycast target
  • If the Raycast target has or is a child of any ICanvasRaycast filter component (at any depth), and the Raycast filter component allows Raycast.

Then, sort the list of hit Raycast targets by depth, filter the reverse targets, and filter them to ensure that the elements presented behind the camera (that is, invisible on the screen) are removed.

If the corresponding flag is set on the "blocking object" property of the graphic Raycaster, the graphic Raycaster can also project rays into the 3D or 2D physics system. (In the script, this property is named Block ingObjects.)

If 2D or 3D blocking objects are enabled, any Raycast targets drawn below the 2D or 3D objects on the raycast blocking physical layer will also be removed from the hit list.

Then return to the last click list.

Raycasting optimization tips

Given that all Raycast targets must be tested by the graphical Raycaster, the best practice is to only enable the "Raycast Target" setting on UI components that must receive pointer events. The smaller the Raycast target list, the shallower the level that must be traversed, and the faster each Raycast test can be.

For composite UI controls that have multiple drawable UI objects that must respond to pointer events, such as buttons whose background and text are desired to change colors, it is usually best to place a Raycast Target at the root of the composite UI control. When a single Raycast Target receives a pointer event, it can forward the event to each component of interest in the composite control.

Hierarchy depth and raycast filters

When searching for a raycast filter, each graphic Raycast will traverse the transformation hierarchy to the root. The cost of this operation increases in proportion to the depth of the hierarchy. All components connected to each transformation in the hierarchy must be tested to determine if they implement ICanvasRayCastFilter, so this is not a cheap operation.

There are several standard UnitedUI components that can use ICanvasRayCastFilter, such as CanvasGroup, Image, MASK and RectMask2D, so this traversal cannot be ignored.

Sub-canvases and the OverrideSorting property

The overrideSorting property on the sub-canvas will cause the graphical Raycast test to stop climbing the conversion hierarchy. If it can be enabled without causing sequencing or ray broadcast detection problems, it should be used to reduce the cost of traversing the ray broadcast hierarchy.

Guess you like

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