Optimized Frame Pacing does not work with Application.targetFrameRate and results in very low FPS

When Optimized Frame Pacing is disabled, setting Application.targetFrameRate works as expected.
On PC the measured FPS match the targetFrameRate and on Android the FPS are an integer fraction of the screen Hz that is close to targetFrameRate. I.e. for a 60Hz screen adjusting the targetFrameRate I get:

=60 => 60FPS (as expected)
41-59 => 60FPS (debatable, but fine for me)
25-40 => 30FPS
18-24 => 20FPS
14-17 => 15FPS
lower values are matched with at most 1FPS difference.

However, with Optimized Frame Pacing enabled, the FPS are unstable and very low when setting targetFrameRate to anything lower than the screen Hz:

=60 => 60FPS, stable
58-59 => 30FPS, stable
~40 => 15FPS, stable initially, then fluctuating
any lower targetFrameRate value results in fluctuating FPS that are roughly 40% of it. E.g:
50 => 17-21FPS, fluctuating
30 => 10-15FPS, fluctuating

In the following posts I will show screenshots of the profiler with examples of the above categories. (I was not allowed to put them all in one post.)

Device: OnePlus 10 Pro, Android 14
Unity 2023.2.8, URP

Is there anything that could be wrong with my settings?
Is this an issue specific to my device or software versions?
Or is this a bug in Optimized Frame Pacing?

I’d try the latest beta and if it still happens file a bug report and link this page

With Optimized Frame Pacing
At targetFrameRate = 60, rendering is smooth and stable at the desired framerate.
However, Gfx.PresentFrame waits on the GPU until the frame time is up, which makes it impossible to see how long the actual rendering work took.

At targetFrameRate = 59, it renders at a stable 30FPS.
Waiting happens in Gfx.PresentFrame on the GPU and in WaitForTargetFPS on the CPU and I don’t understand what is going on.

At targetFrameRate = 30, it initially renders at a more or less stable 15FPS, but breaks into fluctuating FPS after some time or after switching targetFrameRate a bit back and forth.
All waiting happens in WaitForTargetFPS on the CPU.
Gfx.PresentFrame is short and it is possible to see how long the RenderThread is actually busy.

At targetFrameRate = 20 or also later at 30, rendering is extremely choppy and the FPS fluctuate between 10 and 20.
All waiting happens in WaitForTargetFPS on the CPU and the timeline looks the same for “long” and “short” frames, differing only in the length of WaitForTargetFPS.

Without Optimized Frame Pacing

At targetFrameRate = 60 (and anything above 40), rendering is smooth and stable at 60FPS.
Waiting happens in Gfx.PresentFrame on the GPU and in WaitForTargetFPS on the CPU and I’m not sure what is going on. Maybe the hardware is not ready yet after the WaitForTargetFPS completed so Gfx.presentFrame has to wait on the display vSync and this carries over into the next frame with Gfx.WaitForPresentOnGfxThread.

At targetFrameRate = 30, rendering is smooth and stable at the desired 30FPS.
All waiting happens in WaitForTargetFPS on the CPU.
I guess the wait there from the previous frame was long enough for the display to be ready immediately and that is why Gfx.PresentFrame is so short here?

At targetFrameRate = 20, rendering is smooth and stable at the desired 20FPS.
All waiting happens in WaitForTargetFPS on the CPU, same as before.

Hello @detzt , Did you find a solution for this ?

I submitted a bug report and wrote quite a bit back and forth with support. Here are some of the key insights:

Abbreviations used in the following:

OFP = Optimized Frame Pacing
TFR = targetFrameRate (set via script)
FPS = frames per second (measured results)
@xxHz = The refresh rate of the display

  • On Android only integer divisions of the display refresh rate are possible as actual FPS. I.e. on a fixed rate 60Hz display only 60, 30, 20, 15, 12,… FPS are possible. On a variable refresh rate (VRR) display that supports 120Hz, 90Hz, 60Hz, 30Hz, and 10Hz there are a few more possibilities: 120, 90, 60, 45, 40, 30, 24, 22.5, 20, 18, … FPS. The frame pacing algorithm can then choose a FPS for a given TFR, given that the application runs fast enough.
  • OFP chooses at which FPS to render differently than the default frame pacing for the same TFR. They confirmed the issue and you can vote on it here: Unity Issue Tracker - [Android] Application.setTargetFramerate different results when Optimized Frame Pacing is used
  • My device has a variable refresh rate display and this causes several issues when OFP is enabled:
    • For TFR=59, OFP chooses to render at 30FPS @60Hz instead of rounding to 60 or switching the display refresh rate to 90Hz and then rendering at 45FPS.
    • The chosen FPS for a given TFR depends on the previous TFR as well. E.g. TFR=45 results in 45FPS @90Hz and then lowering the TFR to 44 results in 22.5FPS @90Hz. However, when starting with TFR=40 it renders at 40FPS @120Hz and then increasing the TFR to 44 results in 40FPS @120Hz. This means OFP does not consider switching the display refresh rate to improve FPS. Support didn’t comment on this, I probably have to submit another bug report to get this recognized.

This explains where my initial issues were coming from: When OFP is enabled there are certain combinations of TFR and current display Hz that result in halved FPS. In those cases the FPS actually increase when you lower the TFR…

So I ended up disabling OFP for now.

2 Likes

Thank you @detzt , So outside of the solution proposed by @StarBust999 here Unity 2021/2022 laggy/stuttering Android performance compared to Unity 2020 - #9 by weiping-toh , it seems there is still no way to have both OFP disabled and a stable 60frame rates across devices …