We recently wrapped up GDC 2019, where we spoke about Adaptive Performance during our Keynote. We’re excited to let you know that the Preview version and the Megacity mobile sample are now available so you can get started exploring this feature. This blog explains more about Adaptive Performance and how to apply it to your own projects.

Unlike for a PC or console game, harnessing the full power of mobile hardware requires a delicate balance for games to look beautiful and play smoothly. Maxing out a device’s capabilities can quickly compromise your game’s performance by overtaxing the hardware, which leads to throttling, poor battery life, and inconsistent performance. For developers, this issue becomes even more problematic considering the wide range of low-end to high-end target devices.

Today, developers take different tactics to solve this problem. The two main approaches we’ve seen are: trying to make sure games perform at their best on all target hardware, which means sacrificing graphics fidelity and frame rate, or attempting to anticipate hardware behavior, which is really difficult because there are not many options to precisely measure hardware trends.

How Adaptive Performance works

Adaptive Performance provides you with a better way to manage thermals and performance of your games on a device in real time, allowing you to proactively adjust on-the-fly performance and quality settings of your game and utilizing the hardware without overtaxing the device. The result is a predictable frame rate and a decrease in thermal buildup, enabling longer play times and a much more enjoyable player experience while preserving battery life.

For developers, it means having a new, deeper insight into hardware with new tools to make your games more dynamic and flexible, providing your players with the smoothest and best-performing experiences when they’re playing on mobile devices. It gives you control over decisions that usually the operating system makes, such as when to run at high clock speeds or what to adjust to avoid throttling.

We gave several talks about this feature during GDC 2019. You can view the slide deck here and watch the Unity GDC Booth Talk – Megacity on mobile: How we optimized it with Adaptive Performance below.

Partnering with Samsung on Adaptive Performance

We’ve partnered with Samsung, the world’s largest Android mobile device manufacturer, to help bring this solution to fruition. Built on top of Samsung’s GameSDK, Adaptive Performance will first be available for Samsung Galaxy devices such as the Samsung Galaxy S10 and Galaxy Fold, followed by additional Samsung Galaxy devices later this year.

Early results

These charts (shown during our Unity at GDC 2019 keynote) illustrate how Adaptive Performance helps deliver a steady high frame rate with Megacity running on the new Samsung Galaxy S10.

In red, you can see the frame rate in Megacity before we added Adaptive Performance; and in blue, you can see the results after we added Adaptive Performance. With Adaptive Performance, the demo runs at 30 fps for a much longer time and is much more stable.

Why Megacity?

Megacity is a futuristic, interactive city featuring millions of entities, demonstrating how Unity can run even the most complex projects on current-gen mobile hardware. It showcases the latest advances in our Data-Oriented Technology Stack (DOTS), the name for all projects under our “Performance by Default” banner, including Entity Component System (ECS), Native Collections, C# Job System, and the Burst Compiler. Megacity was first presented at Unite Los Angeles 2018 and was released for desktop during GDC 2019.

Megacity is the right project to demonstrate a sample implementation of Adaptive Performance, as it provides us with the flexibility to adapt the game dynamically and proactively to best utilize the hardware. Adaptive Performance was built with scalability in mind, which works great with the principles of DOTS used to build the foundation in Megacity.

The mobile version of the project has 4.5M mesh renderers, 200K building components, 100K audio sources, and more than 6M entities – an ideal candidate for demonstrating Adaptive Performance’s capabilities.

How Adaptive Performance works (in Megacity)

After you install Adaptive Performance via the Unity Package Manager, Unity automatically adds the Samsung GameSDK subsystem to your project when you build to a device. During runtime, Unity creates and starts an Adaptive Performance Manager on supported devices, which provides you with feedback about the thermal state of the mobile device. You can subscribe to events or query the information from the Adaptive Performance Manager during runtime to react in real-time; otherwise, it will only report the stats to the console.

As an example, you can use the API provided to create applications that react to the thermal trends and events on the device. This ensures constant frame rates over a longer period of time while avoiding thermal throttling, even before throttling begins. In the sample implementation of Adaptive Performance in Megacity, we used three different ways to smooth the frame rate:

By starting at moderate CPU and GPU levels, and increasing them gradually to eliminate bottlenecks, we were able to keep energy consumption low.

If we saw that the device was getting close to throttling, we could tune quality settings to reduce thermal load – and we decided to lower the LOD levels.

We also decreased the target frame rate once we were close to throttling.

When the target frame rate is reached and temperature is in decline, we increase LOD levels, raise target frame rate, and decrease CPU and GPU levels again.

These capabilities enable your game to achieve a smoother performance over time. By keeping a close eye on a device’s thermal trends, you can adjust performance settings on the fly to avoid throttling altogether.

Download the Megacity mobile sample project here, to see how we’ve done this. For feedback or questions about Megacity, please visit this forum thread.

Adaptive Performance Manager

The heart of the package is the Adaptive Performance Manager, which Unity creates during startup, allowing you to access and subscribe for thermal and performance event notifications easily. The example below shows how to access the Adaptive Performance Manager using the IAdaptivePerformance interface in the Start function of your MonoBehaviour.

private IAdaptivePerformance ap = null; void Start() { ap = Holder.instance; } 1 2 3 4 5 6 private IAdaptivePerformance ap = null ; void Start ( ) { ap = Holder . instance ; }

Thermal events

Unity sends thermal events whenever there are changes in the thermal state of the device. The important states are when throttling is imminent and when throttling is occurring. In the example below, you subscribe to ThermalEvents to reduce or increase your lodBias, which helps to reduce GPU load.

using UnityEngine; using UnityEngine.Mobile.AdaptivePerformance; public class AdaptiveLOD : MonoBehaviour { private IAdaptivePerformance ap = null; void Start() { if (Holder.instance == null) return; ap = Holder.instance; if (!ap.active) return; QualitySettings.lodBias = 1; ap.ThermalEvent += OnThermalEvent; } void OnThermalEvent(object obj, ThermalEventArgs ev) { switch (ev.warningLevel) { case PerformanceWarningLevel.NoWarning: QualitySettings.lodBias = 1; break; case PerformanceWarningLevel.ThrottlingImminent: QualitySettings.lodBias = 0.75f; break; case PerformanceWarningLevel.Throttling: QualitySettings.lodBias = 0.5f; break; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using UnityEngine ; using UnityEngine . Mobile . AdaptivePerformance ; public class AdaptiveLOD : MonoBehaviour { private IAdaptivePerformance ap = null ; void Start ( ) { if ( Holder . instance == null ) return ; ap = Holder . instance ; if ( ! ap . active ) return ; QualitySettings . lodBias = 1 ; ap . ThermalEvent += OnThermalEvent ; } void OnThermalEvent ( object obj , ThermalEventArgs ev ) { switch ( ev . warningLevel ) { case PerformanceWarningLevel . NoWarning : QualitySettings . lodBias = 1 ; break ; case PerformanceWarningLevel . ThrottlingImminent : QualitySettings . lodBias = 0.75f ; break ; case PerformanceWarningLevel . Throttling : QualitySettings . lodBias = 0.5f ; break ; } } }

Note that if you reduce the lodBias below a value of 1, it will have a visual impact in many cases and LOD object-popping might occur, but it is an easy way to reduce graphics load if it is not required for the game experience. In case you want to make even more detailed decisions to fine-tune how your game’s graphics and behavior are handled, the bottleneck events are very useful.

CPU and GPU performance levels

The CPU and GPU of a mobile device make up a very large part of its power utilization, especially when running a game. Typically, the operating system decides which clock speeds are used for the CPU and GPU.

CPU cores and GPUs are less efficient when running at their maximum clock speed. Running at high clock speeds overheats the mobile device easily and the operating system throttles the frequency of the CPU and GPU to cool down the device.

You can avoid this situation by limiting the maximum-allowed clock speeds with these properties:

IAdaptivePerformance.cpuLevel

IAdaptivePerformance.gpuLevel

The application can configure those properties based on its special knowledge about the current performance requirements and decide, based on the scenario, if the levels should be lowered or raised.

Did the application reach the target frame rate in the previous frames?

Is the application in an in-game scene or in a menu?

Is a heavy scene coming up next?

Is an upcoming event CPU or GPU heavy?

Will you show ads that do not require high CPU/GPU levels?

public void EnterMenu(){ if (!ap.active) return; // Set low CPU and GPU level in menu ap.cpuLevel = 0; ap.gpuLevel = 0; // Set low target FPS Application.targetFrameRate = 15; } public void ExitMenu(){ // Set higher CPU and GPU level when going back into the game ap.cpuLevel = ap.maxCpuPerformanceLevel; ap.gpuLevel = ap.maxGpuPerformanceLevel; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void EnterMenu ( ) { if ( ! ap . active ) return ; // Set low CPU and GPU level in menu ap . cpuLevel = 0 ; ap . gpuLevel = 0 ; // Set low target FPS Application . targetFrameRate = 15 ; } public void ExitMenu ( ) { // Set higher CPU and GPU level when going back into the game ap . cpuLevel = ap . maxCpuPerformanceLevel ; ap . gpuLevel = ap . maxGpuPerformanceLevel ; }

An early-warning system for performance bottlenecks

In the Adaptive Performance Manager, you can subscribe to receive performance bottleneck events that let you know if you are GPU, CPU, or “frame-rate bound.” Frame-rate bound means that the game is limited by Application.targetFrameRate, in which case the application should consider lowering its performance requirements.

Running in the background governing bottleneck decisions – and queryable via the Manager – is the GPU frametime driver, which monitors the hardware time the GPU spent on the last frame; for the moment, the CPU time is calculated by summing Unity’s internal subsystems. Depending on the game and scenario, you can have it react differently when the game is CPU or GPU bound according to thermal state changes.

void OnBottleneckChange(object obj, PerformanceBottleneckChangeEventArgs ev) { switch (ev.bottleneck) { case PerformanceBottleneck.TargetFrameRate: if (ap.cpuLevel > 0) { ap.cpuLevel--; } if (ap.gpuLevel > 0) { ap.gpuLevel--; } break; case PerformanceBottleneck.GPU: if (ap.gpuLevel < ap.maxGpuPerformanceLevel) { ap.gpuLevel++; } break; case PerformanceBottleneck.CPU: if (ap.cpuLevel < ap.maxCpuPerformanceLevel) { ap.cpuLevel++; } break; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void OnBottleneckChange ( object obj , PerformanceBottleneckChangeEventArgs ev ) { switch ( ev . bottleneck ) { case PerformanceBottleneck . TargetFrameRate : if ( ap . cpuLevel > 0 ) { ap . cpuLevel -- ; } if ( ap . gpuLevel > 0 ) { ap . gpuLevel -- ; } break ; case PerformanceBottleneck . GPU : if ( ap . gpuLevel < ap . maxGpuPerformanceLevel ) { ap . gpuLevel ++ ; } break ; case PerformanceBottleneck . CPU : if ( ap . cpuLevel < ap . maxCpuPerformanceLevel ) { ap . cpuLevel ++ ; } break ; } }

There are many different ways to optimize games, and the samples above and in Megacity only provide some suggestions for how to do it; ultimately, it depends very much on what works best for your game. For more information, please also check the package documentation.

What’s next for Adaptive Performance

This is only the beginning! We are going to continue to invest in Adaptive Performance, adding more features and supporting more devices over time. The current package includes a low-level API, but we are already working on a high-level, component-based API compatible with DOTS, which should make it even easier to adapt performance in your Unity projects. Stay tuned for more information.

Get started today

A Preview version of Adaptive Performance is available now for Unity 2019.1 (beta) via the Unity Package Manager. You can access it here. For up-to-date information on Adaptive Performance, to see how other developers are using it, and to post questions or comments, please visit the forum.