This page describes the best practices for managing elements in the visual treeAn object graph, made of lightweight nodes, that holds all the elements in a window or panel. It defines every UI you build with the UI Toolkit.
See in Glossary.
Elements pooling is to keep hold of elements that you might recreate later on, rather than creating elements with new()
every time and letting go of them.
It’s important to be fully in control of all elements that you pool and make sure you reset them properly before you return them to the pool. Otherwise, the pooling system can become unstable and troublesome. For example, it’s impossible to clean up an element if you pool elements while registering event callbacks or setting an internal non-serialized state at the same time.
To keep the number of visual elementsA node of a visual tree that instantiates or derives from the C# VisualElement
class. You can style the look, define the behaviour, and display it on screen as part of the UI. More info
See in Glossary low, use ListView when possible. ListView pools elements and recycles elements as the user scrolls.
Alternatively, you can implement your own pool and recycle mechanism similar to the ListView, and use the following to manage the visible area:
GeometryChangedEvent
VisualElement.layout
propertyWhen you use VisualElement.RemoveFromHierarchy()
to remove an element from the hierarchy and eliminate references to it, the element is garbage collected. This reduces the CPU and GPU cost to zero and frees a significant amount of memory. However, it’s a slow and costly operation to recreate elements and reload them in a hierarchy. To avoid this, you can pre-create the elements in a hierarchy, use USS style properties to hide them, and only display them when necessary. While applying styles is generally faster, it can lead to increased memory usage if you create a large number of elements simultaneously.
The following describes the different approaches to hiding elements and the consequences on processors and memory usage.
visibility: hidden;
With this approach, the descendants can override the visibility
style.
The following table describes the different aspects of single-frame cost when you hide or display a visual element with the visibility
style:
Aspect | visibility: hidden; |
visibility: visible; |
---|---|---|
Styles | Evaluated for the element and descendants to propagate the visibility. | Evaluated for it and the descendants, to propagate the visibility. |
Layout data | Preserved | None |
Rendering commands | Removed and deallocated | Recreated and reinserted into the chain of commands. |
Meshes | Scheduled for deallocation. | Re-tessellated |
The following table describes the per-frame behavior for CPU and GPU when you hide a visual element with the visibility
style:
Processor | Aspect | Per-frame behavior |
---|---|---|
CPU | Styles | Fully evaluated to the element and its descendants. |
Layout data | Updated | |
Tessellation | Minimal impact that only involves stencil masking meshesOverflow hidden with either rounded corners or vector image background. See in Glossary, if applicable. |
|
Rendering commands | No commands to draw regular visible geometry. However, stencil masking meshes are still rendered to push or pop from the stencil, ensuring potential visible descendants are masked. | |
GPU | Meshes | Vertex and fragment shading on stencil masking meshes. |
opacity: 0;
With this approach, the GPU usage can be high if the content is in the ViewportThe user’s visible area of an app on their screen.
See in Glossary, as the fragment shaderA program that runs on the GPU. More info
See in Glossary processes all elements, potentially leading to significant overdraw.
The following table describes the single-frame cost when you hide or display a visual element with the opacity
style:
Action | Single-frame cost |
---|---|
opacity: 0; |
The first time when you set the opacity to a value other than 1 , the UI(User Interface) Allows a user to interact with your application. Unity currently supports three UI systems. More infoSee in Glossary Toolkit renderer modifies the vertices to accelerate the application of the opacity on GPU. This triggers a one-time, minimal CPU cost. While typically negligible, this cost might become noticeable if the element has a large number of descendants or if there are many vertices to modify. This cost is not incurred again unless the element is removed from the visual tree and re-added. |
opacity: 1; |
None |
The following table describes the per-frame behavior for CPU and GPU when you hide a visual element with the opacity
style:
Processor | Aspect | Per-frame behavior |
---|---|---|
CPU | Styles | Fully evaluated to the element and its descendants. |
Tessellation | Operates normally and responds to changes. | |
Rendering commands | Executed | |
GPU | Meshes | The vertex shaderA program that runs on each vertex of a 3D model when the model is being rendered. More info See in Glossary operates as though the visibility is set to 1 . Similarly, the fragment shader also functions as if the visibility is 1 . This can be detrimental in GPU-bound projects as it can lead to overdraw. |
display: none;
With this approach, the element behaves like it is removed from the layout tree, which might potentially affect the layout of other elements.
The following table describes the different aspects of single-frame cost when you hide or display a visual element with the display
style:
Aspect | display: none; |
display: flex; |
---|---|---|
Layout data | Might recompute the layout of other elements. | Pending layout changes are processed. |
Rendering commands/Meshes | Regenerated for elements affected by the layout change. |
|
The following table describes the per-frame behavior of the CPU when you hide a visual element with the display
style. Note that there’s no GPU cost.
Aspect | Per-frame behavior |
---|---|
Layout data | Preserved but can become invalidated and not being updated. |
Rendering commands | Although retained, they can be skipped during execution. The way they are skipped is very cheap but not entirely free. The cost is proportional to the number of commands. |
Meshes | Retained but can become invalidated and not being updated. |
You can use translate: -5000px -5000px;
combined with DynamicTransform
usage hints to move the elements out of the Viewport. The geometry remains fully active, resulting in minimal CPU usage when you bring back the element on the screen. However, the GPU continues to process the vertices, which might be acceptable depending on the scenario.
The transform is computed and uploaded into GPU memory, which is generally fast.
The following table describes the per-frame behavior for CPU and GPU when you hide a visual element by translating it outside of the Viewport:
Processor | Aspect | Per-frame behavior |
---|---|---|
CPU | Styles | Updated |
Layout date | Updated | |
Draw calls | Executed | |
GPU | Meshes | Vertices are shaded. |
When you use the VisualElement.RemoveFromHierarchy()
method to remove the element from the hierarchy, you free up CPU and GPU memories, thereby eliminating any computing costs.
The following table describes the single-frame cost when you hide or display a visual element by removing it from the hierarchy:
Aspect | Remove | Add |
---|---|---|
Styles | None | Updated for the subtree. |
Layout | None | Recomputed for the subtree and possibly other elements. |
Rendering commands/meshes | Regenerated for elements affected by the layout change. |
|
The following table summarizes the memory usage after the element is hidden with different approaches:
Processor | Aspect | visibility:hidden; |
opacity:0; |
display:None; |
Translated out of Viewport | Removed from the hierarchy |
---|---|---|---|---|---|---|
CPU | Styles | Retained | Retained | Retained | Retained | Freed |
Layout | Retained | Retained | Retained | Retained | Retained[1] | |
Rendering commands/meshes | Freed | Retained | Retained | Retained | Freed | |
GPU | Meshes | Freed | Retained | Retained | Retained | Freed |
The layout memory is retained because it remains reserved for the element. When the VisualElement is garbage collected, the layout memory is returned to the pool, making it available for use by other elements. ↩