skip to main content

Animation Budget Allocator

ABA Enabled with 128 Characters

Introduction

The purpose of the Animation Budget Allocator (ABA) is to manage the performance of all skeletal mesh animations on the CPU by assigning a significance to each of them. Based on this significance the ABA will reduce the overall animation costs by reducing the URO (Update Rate Optimisation), which essentially means skipping several frames of an animation each cycle. Ideally, this means that objects closer to the camera - the ones the player is probably paying more attention to - will animate at a higher quality than those further away. Think of it like Animation LODs.

Enabling the Animation Budget Allocator System

In order to use this tool the Animation Budget Allocator plugin will need to be enabled and added as a dependency to the project (see Unreal plugin documentation).

Once the plugin is enabled, the ABA backend must also be explicitly enabled. This must be done per UWorld by calling the FAnimationBudgetAllocator::SetEnabled() method, accessing the appropriate object via IAnimationBudgetAllocator::Get(UWorld* InWorld) AND globally by setting the console variable a.Budget.Enabled 1.

While the console variable can be set at runtime the Animation Budget Allocator doesn’t like swapping states when any skeletal mehes are animating, so ideally a.Budget.Enabled should be set in an approprate .ini file. A project using this most likely wants it enabled permenantly anyway!

auto* Interface = IAnimationBudgetAllocator::Get(GetWorld());
Interface->SetEnabled(true);

USkeletalMeshComponentBudgeted

What ties the Animation Budget Allocator and our skeletal meshes together is the USkeletalMeshComponentBudgeted. This class inherits from USkeletalMeshComponent and contains all the budget specific code. The USkeletalMeshComponentBudgeted component will work out of the box, and should be used wherever a USkeletalMeshComponent would’ve been used previously.

AnimationBudgetAllocator

The IAnimationBudgetAllocator is has a static Get() method which allows interfacing with the FAnimationBudgetAllocator for a given world context. Beyond enabling the system, the most common use case for accessing this is manually registering USkeletalMeshComponents.

The FAnimationBudgetAllocator works by having a set budget that is calculated based on the current performance. This budget is that spread out between all the registered USkeletalMeshComponents based on their significance. A higher significance means a higher budget allocation for a given component.

The overall budget in milliseconds is set via a.Budget.BudgetMs allowing a default to be specified in an ini file, and for experimental runtime tweaks. 

Adding to the Allocator

The most straightforward way to have a component register with the Allocator is through bAutoRegisterWithBudgetAllocator. In the editor this can be found on the USkeletalMeshComponentBudgeted details panel, under Budgeting and is true by default.

Shows the details panel of the USkeletalMeshComponent
USkeletalMeshComponent Details Panel

Alternatively, as the Animation Budget Allocator Interface is static, you can register a component from anywhere, if a valid world is accessible.

#include "IAnimationBudgetAllocator.h"

IAnimationBudgetAllocator::Get(GetWorld())->RegisterComponent(this);

Significance

In the USkeletalMeshComponentBudgeted class you will notice a static delegate named OnCalculateSignificanceDelegate. Binding to this delegate will override the significance calculator logic. By default, significance will be calculated based purely on the distance between the player and the relevant animating actor. Slightly more sophisticated calculations could take account of the distance and dot product (better simulating the player’s field of view), or size of the animating actor on screen.

At the time of writing, there is an issue with this original calculation. Existing engine code (UE5.1) takes all controllers into account and not just the players'. We have submitted a small fix for this issue in the following https://github.com/EpicGames/UnrealEngine/pull/10074.

const FVector WorldLocation = GetComponentTransform().GetLocation();
const UWorld* ComponentWorld = GetWorld();
for (FConstControllerIterator It = ComponentWorld->GetControllerIterator(); It; ++It)
{
	if(const AController* PlayerController = It->Get())
	{
	     ...
	}
}

Changing all AController types to APlayerController inside the USkeletalMeshComponentBudgeted::TickComponent method will prevent AI controllers being taken in to account. Note a more sophisticated approach may still be needed depending on the exact requirements of a given title.

What the Significance Debug looks like in Game.
Significance Debug

With the default Significance Calculator, the significance value will range anywhere from 0.0f-1.0f. A significance of 1.0 implies a higher priority to an object with a 0.0f significance. This means that once the Budget has been calculated that frame, more of it will be priotised to the higher sigifance objects.

This whole interaction with signficance means it plays well into the Significance Manager Plugin.

Optimisation in Action

Enabling the Budget Debugger (a.Budget.Debug.Enabled 1), will show a graph along with a variety of values on each of the components.

The dotted line on the graph represents the budget it is aiming for, while the solid line is what the performance actually is. On start, the animations will immediately be prioritised in order to hit the target budget, and this is clearly visible in the debugger. The budget can be changed at runtime with a.Budget.BudgetMs.

The number shown above each component’s owning actor is simply the significance of that object.

With the default significance algorithm in use setting a.Budget.AutoCalculatedSignificanceMaxDistance 3500 will give a better representation of the significance value.

There’s plenty of further CVars for a lot of fine-tuning, so be sure to look inside AnimationBudgetAllocatorCVars.cpp.

The ABA Debug Graph
Animation Budget Allocator Graph

Performance Benchmarking

The net-gain in performance can be visualised with the stat fps and stat anim console commands. The figures below were generated in a cooked build of First Person Template with the following set: r.Vsync 0, a.ParallelAnimEvaluation 0, a.ParallelAnimUpdate 0, a.ParallelAnimInterpolation 0.

ABA Off - 64 Characters
ABA Off - 64 Characters
ABA On - 64 Characters
ABA On - 64 Characters

In the stat anim view, AnimGameThreadTime gives the best idea of performance. It’s also good to look at the SkinnedMeshCompTick’s CallCount, as when budgeting is off the call count is the exact number of Budgeted Components.

Statistics

Benchmarked in an empty map using the development configuration in a cooked build using increasing numbers of humanoid AI.

The first person character's arms have also been included in the budgeting.

BudgetMs was the default at 1.0ms.

      AnimGameThreadTime SkinnedMeshComp Tick
Optimisation  AI Count  FPS (Call Count / Time (ms)) (Call Count / Time (ms))
 
Off 16 73 36 / 3.5 19 / 3.5
On 16 73 9 / 1.19 6 / 1.2
 
Off 32 63 68 / 7.0 35 / 7.0
On 32 64 15 / 2.2 9 / 2.2
 
Off 64 49 132 / 14.2 67 / 14.4
On 64 52 28 / 4.0 16 / 4.0
 
Off 128 25 260 / 29.3 131 / 29.3
On 128 39 55 / 7.6 29 / 7.7
 
Off 256 12 516 / 60.8 259 / 61.9
On 256 27 107 / 15.1 55 / 15.3

 

As shown in the table, the Animation Budget Allocator is always superior when optimised, but particularly scales better at higher component counts. The only caveat is that after ~128-255, the quality of the animations degrades to the point where it's immersion breaking.

Conclusion

Overall, the Animation Budget Allocator is a neat plugin that doesn’t require too much set-up to get going, and the auto-adjustment of optimisation based on performance means it’s it scales up well. Since it follows a similar ethos as environmental LODs, it makes sense to implement this as higher fidelity animations will not enhance lower LOD models!

Credit(s): Christopher Robertson (d3t)

Support: Josef Gluyas (Coconut Lizard)

Facebook Messenger Twitter Pinterest Whatsapp Email
Go to Top