githubEdit

Measuring Layout Calls

Introduction

Gum's layout engine, although fairly efficient, can introduce performance problems for complex scenes, or scenes which are updated frequently. This page discusses:

  • Gum's default layout behavior

  • How to measure layouts for performance problems

  • How to improve your layout performance

Default Layout Behavior

GraphicalUiElements (visuals for controls) provide many variables for controlling absolute position and size. These include:

  • Position values such as X and Y

  • Size values such as Width and Height

  • Unit values such as XUnits or WidthUnits

  • Rotation

A GraphicalUiElement's absolute position or size can also be affected by its Parent, Children, and even siblings. Therefore, changing any of these values can result in one or more objects performing a layout.

Layouts are performed immediately after a value is set so that absolute values are (by default) always up-to-date.

For example, we can see that a StackPanel's Visual always reflects its size after every child is added.

StackPanel stackPanel = new();
stackPanel.AddToRoot();
stackPanel.Anchor(Anchor.Center);

for(int i = 0; i < 10; i++)
{
    float heightBefore = stackPanel.Visual.GetAbsoluteHeight();

    Label label = new();
    stackPanel.AddChild(label);

    float heightAfter = stackPanel.Visual.GetAbsoluteHeight();

    label.Text = 
        $"Label {i + 1} (StackPanel Height: {heightBefore} -> {heightAfter})";
}
StackPanel displaying its size

This code shows that a StackPanel's absolute height is calculated after every AddChild call. Note that the absolute height is calculated whether we call GetAbsoluteHeight or not - this method simply retrieves the already-calculated value.

This behavior has the benefit of an object always updating its most up-to-date position and size, but it has the downside of performing potentially unnecessary layout calls.

Most of the time these extra layout calls don't have an impact on performance; however complex layouts may slow down projects due to this behavior.

Measuring Layouts

The number of layout calls which have been performed can be obtained from the GraphicalUiElement.UpdateLayoutCallCount static property. This reports the total number of layouts, so usually a before-and-after is often useful.

We can modify the code above to display layout call counts when each label is created. Note that this code increases to create 20 Label instances:

Layout calls
circle-info

Although each call count has a slight performance cost, Gum can efficiently perform hundreds of layout calls. Most of the time these layout will not cause performance problems for games.

Also, the layout calls displayed above are for illustrative purposes. The exact number of calls is not important, and these call counts are likely to change as future versions of Gum are released.

We can notice a few things when looking at this code:

  1. UpdateLayoutCallCount increases after every item is added

  2. The amount of UpdateLayoutCallCount increase grows as more items are added to the StackPanel

Based on the screenshot above, our project is performing over 1000 unnecessary layout call counts. At least, in a typical game the final layout is only needed when drawing is performed, not after every item add.

It's important to note that a single control may perform multiple layout calls when a single variable is changed. For example, consider the following code:

You may expect the code above to perform only 2 layout calls, but we see the number is actually higher:

Number of layout calls displayed by a button

Remember that even a simple control, like a Button, is made of multiple visuals:

  • The main Visual itself

  • The text

  • The background

Therefore, changing the button's Width requires multiple visuals to update their layout.

Reducing Layout Calls

We can improve the performance of our code by setting GraphicalUiElement.IsAllLayoutSuspended to true, then resuming layout it after the adds have finished:

Significantly reduced layout calls

States and Layout Calls

States can be used to set multiple variables at once. Internally Gum automatically suppresses and resumes layouts when states are applied. The following code shows how to apply states to a button. Notice that fewer layout calls are performed compared to explicitly setting each value:

Setting states can reduce layout call count

Last updated

Was this helpful?