Is this performance expected for 10000 visual elements on screen?

For my test I’m showing 10000 visual elements and once in a second their position is randomly changed.

I’m using hints for parent visual element (UsageHints.GroupTransform) and for each element as well (UsageHints.DynamicTransform). Dynamic elements have position set to absolute.

The question is, is it expected to have this performance for 10k elements (see profiling image below), or there is a hint to make it work even better?

Code

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;


namespace MyTest
{
    public class Test : MonoBehaviour
    {
        public UIDocument uiDocument;

        void Start()
        {
            Application.targetFrameRate = 0;

            var graph = new VisualElement();
            graph.pickingMode = PickingMode.Ignore;
            graph.usageHints = UsageHints.GroupTransform;
            graph.style.backgroundColor = new Color(0.1f, 0.2f, 0.3f);
            graph.style.flexGrow = 1;
            uiDocument.rootVisualElement.Add(graph);

            var list = new List<VisualElement>();
            for (int i = 0; i < 10000; i++)
            {
                var item = new VisualElement();
                item.pickingMode = PickingMode.Ignore;
                item.usageHints = UsageHints.DynamicTransform;
                item.AddToClassList("mt-test-graph-tick");
                graph.Add(item);

                list.Add(item);
            }

            //graph.schedule.Execute(() => { TranslateItems(graph, list); }).ExecuteLater(1000);
            graph.schedule.Execute(() => { TranslateItems(graph, list); }).Every(1000);
        }

        private void TranslateItems(VisualElement graph, List<VisualElement> list)
        {
            var bound = graph.worldBound;

            foreach (var item in list)
            {
                var x = Random.Range(0, bound.width);
                var y = Random.Range(0, bound.height);
                item.transform.position = new Vector3(x, y, 0);
            }
        }
    }
}

USS

.mt-test-graph-tick {
    position: Absolute;
    background-color: rgb(255, 0, 0);
    border-color: rgb(194, 179, 0);
    border-width: 1px;
    width: 4px;
    height: 4px;
    rotate: 45deg;
}

Tested on Unity 2022.2.2

Another use case.

10k elements (no specific hints) attached to a visual element with hint UsageHints.DynamicTransform. Parent element has position absolute and its parent has hint UsageHints.GroupTransform. Then the parent of 10k elements is moved each frame.

Here is profiling screen:

I was expecting better performance. Any hint for this use case?

Thanks.

Code

using UnityEngine;
using UnityEngine.UIElements;


namespace MyTest
{
    public class Test2 : MonoBehaviour
    {
        public UIDocument uiDocument;
        public float speed = 1;

        private VisualElement _graph;
        private float _x = 1000;
        private float _y;

        void Start()
        {
            Application.targetFrameRate = 0;

            //uiDocument.rootVisualElement.usageHints = UsageHints.GroupTransform;

            var ve = new VisualElement();
            ve.pickingMode = PickingMode.Ignore;
            ve.usageHints = UsageHints.GroupTransform;
            ve.style.backgroundColor = new Color(0.1f, 0.2f, 0.3f);
            ve.style.flexGrow = 1;
            uiDocument.rootVisualElement.Add(ve);


            _graph = new VisualElement();
            _graph.pickingMode = PickingMode.Ignore;
            _graph.usageHints = UsageHints.DynamicTransform;
            _graph.style.position = Position.Absolute;
            ve.Add(_graph);

            for (int i = 0; i < 10000; i++)
            {
                var item = new VisualElement();
                item.pickingMode = PickingMode.Ignore;
                //item.usageHints = UsageHints.DynamicTransform;
                item.transform.position = new Vector3(Random.Range(0, 1000), Random.Range(0, 100), 0);
                item.AddToClassList("mt-test-graph-tick");
                _graph.Add(item);
            }
        }

        private void Update()
        {
            if (_graph != null)
            {
                _x -= Time.deltaTime * speed;
                _y += Time.deltaTime * speed * 0.5f;
                _graph.transform.position = new Vector3(_x, _y, 0);
            }
        }
    }
}

USS

.mt-test-graph-tick {
    position: Absolute;
    background-color: rgb(255, 0, 0);
    border-color: rgb(194, 179, 0);
    border-width: 1px;
    width: 4px;
    height: 4px;
    rotate: 45deg;
}

Tested on Unity 2022.2.2

Thanks for bringing this up. This is definitely slower than expected. The main bottlenecks are currently the bounding-box and transform updates. We’ll make more investigations to see if there’s a way to speed this up, but it looks like this is just the cost of re-evaluating some required states for the VisualElements. This is currently implemented in (slow) managed code, but since they are on the hot path we’ll have to optimize them.

Is this for an actual use-case you’d like to implement or are you just evaluating the perf with this example?

Our actual use case is to draw charts/graphs with potentially 10-100k elements on graph. We are looking into ways to use only UI toolkit for that, but I guess for time being we’ll render those items as meshes with a camera into a render texture and then use it in UI.

As a side note, we have “data grids” we currently use MulticolumnListView for, that have performance issues as well.

If the 10-100k elements are all dynamic and moving (as they are in your above example), it will be definitely better to build your own mesh. This way you won’t pay the price of layout/styling of UI Toolkit.

If the 10-100k elements are mostly static, but you need to transform them as a group, adding them to a common GroupTransform parent and transforming that parent should be very efficient.

Can you share more details?

That’s the case we are considering (the second test from above). Still seems to be slow, compared to rendering a mesh to a texture.

Here is an example of one of MulticolumnListView:

8756209--1186603--upload_2023-1-25_17-7-29.png

This list is generated in about a second. Here is a profiling screen:

8756209--1186597--upload_2023-1-25_16-58-21.png

Same list view, but now I’m changing list selection via SetSelectionWithoutNotify:

8756209--1186600--upload_2023-1-25_17-2-5.png

Same list when moving mouse/hovering over rows. Here we have few elements that have their visibility changed by DisplayStyle.Flex/None

8756209--1186609--upload_2023-1-25_17-10-41.png

I do accept that we might’ve done something wrong, or extremely inefficient. But, in our case, performance drops very quickly with the increasing number of components in a list item. I see a similar pattern when doing a test UI with only standard components, though.

Nevertheless, we think UIToolkit is much better than the old UI and we area glad that we’ve migrated to it.

There seems to be an abnormally high CPU time spent in “Update Style”, which is about figuring out which selectors apply to your elements and resolving/building the final style object (responsible for allocations).

We have published this page not long ago to give high level information on how to improve this: Unity - Manual: Best practices for USS

There’s definitely a limit to how much you can improve these numbers only be re-working your selectors, since the number of elements still plays a big factor, but I think it should be possible to get something better based on your screenshots.

Thanks, we’ll definitely look into it.

Meantime we’ve done a small test. Removed all our styles and left only unity default one. There was an improvement, but general picture is similar.

For same list:
8757805--1186969--upload_2023-1-26_10-21-52.png

Here is profiling picture for the case when moving/hovering mouse other the list items:
8757805--1186978--upload_2023-1-26_10-23-59.png