UI Toolkit accumulating hundred+ draw calls when VE's shown/hidden (as reported 8 months ago...)

Background
I reported back in April, 2022 that I had found a crippling problem with how the UI Toolkit was reacting to visual elements being shown and hidden. As discussed here, this was demonstrated at that time by two phenomena:

  1. The vertex count would skyrocket into the millions and not come down.
  2. The draw call count would also go into the hundreds range and not come back down.

Your team reviewed the bug report and eventually suggested problem #2 was due to how the profiler was calculating vertices, and it would be fixed. I was also told there was a deeper change in how the UI Toolkit would be rendering so we needed to wait for that to see about #1

I have downloaded Unity 2022.2 and while I can see it no longer gives abnormal vertex counts, the problem of massive draw call accumulation is still occurring along with heavy performance problems from this.

The Ongoing Problem
Using the exact same project from the prior bug report, here is an initial state of the app showing enough visual elements to add up to around 160K vertices. Despite the large number of elements, it is only running at 7 draw calls and 7 batches which is still quick and responsive:

The test project is designed to show or hide (toggle) a large number of the elements each time you click. Here it is after one click with a large amount of elements hidden, now showing 7.6k vertices:

Despite showing fewer vertices, we now have 79 draw calls and 12 batches.

If we click again to toggle back to the initial state showing the full vertex count and elements, here is what we get:

Now we have 122 draw calls and 48 batches despite showing the same vertex and triangle count as on start up (same state).

Significance
I believe this was not (and is still not) a cosmetic or superficial issue. I am now around 19 months into my app development and I have optimized every aspect so far I can. However, despite every optimization I still cannot get a simple scroll window to scroll smoothly. I think this is because by the time I toggle around panels within the app (ie. navigating from a user perspective) to get to a typical scroll window in the UI, I am already at 150+ draw calls.

I would very much appreciate a reassessment of this still ongoing problem. If there is no obvious solution to your team, I wonder if you can speculate on why this is happening and share the potential reason. Perhaps there is some workaround we can develop.

I don’t mind using Reflection or other tools if I had to. For example, I built a custom text input field using Reflection (with some suggestions from your staff on the forum) since the character positions were not publicly available in the Label or TextCore TextGenerator classes. I don’t mind putting work in myself to solve it if there is any way I can. I need this to work. I cannot permanently have 150+ draw calls and the app barely chugging along every time a user toggles some elements or a UI panel on/off.

If we even had some way to trigger a full “redraw” or “reinitialization” of the draw call system periodically (similar to how we garbage collect) perhaps this would be a manageable solution. I could monitor the draw calls presumably and every time they get too high, trigger a reinitialization. Or just run one whenever the app is visually at rest or after a large panel is shown/hidden.

I appreciate any solutions you can provide.

New Bug Report: IN-25597

At a glance, this doesn’t seem related to the other bug. Can you provide a screenshot of the hierarchical view of the CPU Usage module? For example, the “UIR.DrawChain” performance marker (for both the main thread and render thread) would provide useful data. A screenshot of the timeline view would be useful as well. It would be even better if you could share the frame data (save to file at the top right of the profiler).

Thanks Alexandre! I appreciate your quick reply! At least I know you and your team are paying attention which makes me feel reassured. :slight_smile:

I updated the post with my new bug report IN-25597. The project used to demonstrate the bug is only ~300 lines in a single “AppScript.cs” attached to a game object to build some Visual Elements in a “normal” seeming and scalable appearance/utility.

It is the exact same as the prior project I submitted in the previous report 8 months ago.

The problem can be demonstrated simply by:

  1. Open the project and the profiler.
  2. Press play and view the initial draw call count (~7) and vertex count (~163K).
  3. Click the screen anywhere to hide many of the Visual Elements and check the resulting draw call count (~79) and vertex count (~7K).
  4. Click the screen again to return to the initial visibility state of the visual elements and check resulting draw call count (~125) and vertex count (~163k).

This problem was demonstrated similarly 8 months ago when for example an identical visibility state (as given by triangles, vertices were deemed by Unity staff to be a calculation error) before and after visibility toggling looked like this (copied and pasted from the other thread):

Before Clicking Around:

After Clicking Around:

There you see the same problem where in both conditions I have 4K triangles (again, vertices were miscalculated by Profiler back then but triangles were accurate) yet we went from 12 draw calls to now 115.

I will try to provide all those things you requested as well, but I am not an expert in investigating all those things you mentioned so it will take me some time to figure out. If you are willing to help perhaps I wonder if you could load the project yourself from the bug report (IN-25597) and try it to see what happens. It is as easy as pressing play and clicking the screen twice to trigger. It happens every time 100%, and it is just a tiny project as noted.

Thanks again, and I’ll go work on trying to assemble some of those things as well.

Thanks for submitting a repro!

I opened your project and I can see the increase in the number of draw calls on DX11 (tried OpenGL too). It seems to be related to fragmentation in the vertex/index buffers. However, I’m not seeing high CPU usage caused by these draw calls: UIR.DrawChain is below the millisecond and the render thread is almost idle. On which device/graphics API do you observe a sustained high CPU usage for UIR.DrawChain?

101 Batches, Main Thread:

101 Batches, Render Thread:

That being, said, I’m seeing high CPU usage on the frame where 2088 VisualElements become visible, as you can see below. While this CPU usage has nothing to do with the draw calls themselves, it is of course undesirable and needs to be addressed. One thing to note is that layout update (see above, Update Layout is 79ms) is skipped when you toggle the visibility instead of toggling display.

Did you try virtualizing your UI, so that the geometry of elements that are off-screen doesn’t have to be generated? Before I make other suggestions, there is a number of things I want to validate, and we’ll get back in touch.

1 Like

Thanks Alexandre. You gave me some ideas for quickly testing if I was right or wrong. I was blaming my performance woes on this draw call issue because it seemed likely to me having 150+ draw calls would not be a good thing. But I think that you are correct that it is perhaps not an obvious impediment to performance. These extra UI Toolkit draws are not expensive it seems (as was previously suggested by staff in the other thread).

To demonstrate this, with my test project, I set the Application.targetFrameRate to 1000 to let it run at full speed. Then in the Editor, I compared the Debugged out frame rate (1/Time.deltaTime) while it had a low draw call count (~7) and then after it had the high draw call count (~125). These were giving roughly the same frame rates. This suggests it is not the likely bottleneck at least with this project in Editor.

I can’t test it the same way on my Android phone as Android mobile builds don’t seem to let Unity run faster than the screen refresh rate even with vsync off. You would be able to tell better than me if there’s any unique issue there then. Whether the draw call fragmentation issue then needs to be “fixed” I am not sure.

Either way, at least as far as my performance problems go, it seems I must chase other possibilities further.

I am building for Android/iOS mobile where I need solid performance up to 120 hz. In a game you might not notice a few dropped frames at such a fast rate, but when you are scrolling a UI window (like you would a web browser) and watching the inertia play out, it actually becomes very obvious and disruptive to see any jerky motion even from a few tiny dropped frames here and there.

Thus I have <8 ms per frame on a mobile strength CPU to maintain smooth motion. I must then look at every CPU spike in the profiler timeline where the frame duration goes above this and try to narrow down the cause to iron them all out. In theory if I can hammer down every spike so I am always perfectly running <8 ms per frame then the problem will be solved.

I am not sure if I can solve them all (as some seem to be updates and GUI render things likely beyond my control) but I will at least work towards that next and if there are any I can’t solve I can make a thread for that to inquire for ideas.

Thanks for hopping on this right away and giving me some expert feedback to work from. Sorry if I was too alarmist in my post. I appreciate it and I will keep working on my end.

1 Like

Please take notes that the scrollview update the scrolling at 30fps. This is something we intend on changing but I don’t have any other detail on this.

1 Like

Thanks Simon. Good point to raise. I have built my own scroll views so not affected by that. I have some pretty good ideas for what is getting in my way (and it is my code).

I think I just have very tight performance margins to reach my goal and have to be very careful. I was assuming perhaps falsely this was the problem and waiting for a fix instead of working on it.

I will try to optimize further with the timeline and process of elimination and post back if I hit any walls with any possible lagging unity functions I can’t explain.

1 Like