targetFrameRate not working in 2021.3.1 LTS WebGL

I’m working on a game for WebGL platform, I was earlier working before on Unity 2020 LTS. I fixed the FPS to 60 with the help of Application.targetFrameRate and it was working, but as soon I upgraded my project to 2021 LTS. It stopped working, it only works in editor but on WebGL platform inside browser it adapts to the current refresh rate of monitor.

If i downgrade my project back to 2020 LTS, it works. But if I upgrade it to 2021 LTS it stops working. Nothing changed in project. Is it something I’m missing in 2021 regarding WebGL?

I have also tried setting vsync to 0 via code. It didn’t resolved anything. Its only happening to 2021.3.1 LTS

Update: When i turn vsync off and set it to 0 and set targetFrameRate any value except 60 (even 50 or 61) it works. Its only not working on value of 60

1 Like

If you’re on a high refresh monitor, like a new Macbook Pro has a 120hz monitor, then the logic we have for targetFrameRate will fail for 60 because we have a hard-coded assumption that the browser is running at 60hz. It tries to be smart and if you set it to 60, it uses the native refresh rate instead. It was only recently that Chrome added support for the higher refresh rates, and we’ll need to rethink the logic we have for that.

That said, setting targetRefreshRate will always be less efficient than allowing it to use the native refresh rate, since it would use a timer instead of the browsers native update loop.

Thank you for the detailed response. But what if we really want to force the target frame to 60 on every type of display (120Hz or even 144Hz). You can consider this a very strict hard requirement for us, in any case we want our it to run at 60 at max. Is there any other way?

Because our app on WebGL must run at 60 FPS since its embedded inside a webpage that tries to record and sync with it at 60 FPS, it’s essential to our requirement.

Currently we are working on Unity 2020 with this method, but we would like to upgrade to 2021 version. Please let us know if there is anyway for us to do that.

But, when set targetFrameRate to 15, fps remains 60hz. This didn’t happen until Unity2021.

  1. Set targetFrameRate
    8171795--1063526--upload_2022-6-1_11-23-31.png

  2. Print function called:
    function _emscripten_set_main_loop_timing(mode, value) {
    Browser.mainLoop.timingMode = mode;
    Browser.mainLoop.timingValue = value;
    console.log(‘_emscripten_set_main_loop_timing’, mode, value);

  3. Game’s FPS remains 60hz
    8171795--1063529--upload_2022-6-1_11-24-51.png

Try setting it to some odd number like 14 or 29 or 59 and it will reflect. Its only happening in Unity 2021. Also make sure you set vsync to 0

@OceanX000 Looking at the code that controls emscripten_set_main_loop_timing:

  • Make sure to set the vsync to 0, otherwise it will override the specified targetFrameRate to 60 / targetVSync.

  • If targetFrameRate % 60 == 0, then it will always use EM_TIMING_RAF instead of EM_TIMING_SETTIMEOUT, and the skipFrames will be 60 / targetFrameRate.

So, if targetFrameRate is 15, it should be calling emscripten_set_main_loop_timing(EM_TIMING_RAF, 4).

Unfortunately there is no way in the browser to reliably query or measure the framerate of the monitor, which can also change if you move the window from one monitor to another. This means targetFrameRate of 0 will always use (EM_TIMING_RAF, 0) and play every frame (or as fast as it can), which on Chrome on a 144Hz monitor, will play 144fps (assuming the game can keep up). We’ve talked with Google about this but there is no web spec to query this.

You could always hijack emscripten_set_main_loop_timing like you did to print out the values, and replace the Unity specified values with your own, _emscripten_set_main_loop_timing(0, 1000/60). Note that using a timeout mainloop will likely cause inconsistent and possibly unsmooth playback, because requestAnimationFrame was designed to be used for rendering. Also, it’s possible some browsers, Firefox possibly, might not render correctly if not rendered from a requestAnimationFrame callback.

@brendanduncan_u3d sorry i’m a bit confused for targetFrameRate % 60 == 0 (when targetFrameRate is 15 then it should be false, then how its still using EM_TIMING_RAF)

Also how we can force EM_TIMING_SETTIMEOUT when targetFrameRate is 60

Another problem is we are trying to achieve it closer by setting it to 59 but it causing our frames to freeze for no reason

This is only happening in Unity 2021 (old versions are okay, we would like to achieve it on new version if possible)

I’m sorry, I meant 60 % targetFrameRate, so 60%15 == 0 and it does use RAF.
I don’t know why that would cause your frames to freeze, though in general it’s highly un-recommended to use timeout for rendering. Looking at 2020, it’s the same logic for RAF vs timeout. If the freezing just happens in 2021+, then maybe the newer Emscripten is doing some funny busines with timeout main loops. I’d have to have a repo to test it.

The whole framerate business with requestAnimationFrame is a mess because Google and Apple can’t come to an agreement on how it should work, which makes it a fragmented mess. We haven’t found a robust way to reliably solve the problem yet, though wanting to limit framerates is more of an edge case (obviously one important to you).

Thanks, this fixed my issue! I was wondering, here and in the docs its recommended to let the browser dictate the frame rate. When we run our build the fans on our machine start going crazy and the gpu usage is really high (this is across multiple machines such as M1 Max 24 gpu core and windows machines with 20180ti, 3080 mobile, 3090 desktop and 1080 desktop). What’s shown on the screen seldom changes as most of the time a user is just using it to view static content. So we really don’t need it to be updated every frame, only when being interacted with.

Will limiting the framerate with targetFrameRate to something low really be less efficient than running it at max speed?

Unity2021.3.16LTS
mobile webgl

Need 60 % targetFrameRate != 0

targetFrameRate = 31

QualitySettings.vSyncCount = 0;
Application.targetFrameRate =targetFrameRate;

it works.

I am also very interested in what robrab2000-aa asked: is setting the target frame rate to -1 (i.e. letting the browser decide) really the “better” way when the goal is to be less resource-hungry?
I’m sure I used to be able to limit the frame rate to 30 to not have laptops put their fans into overdrive (or the M1 MacBooks actually produce fan noise), but now I can’t seem to influence the actual frame rate at all, even when using 31 instead of 30 as the target number and setting vSyncCount to zero.
Is there a reliable way to cap the frame rate in WebGL nowadays? ( @brendanduncan_u3d ?)

@uwdlg Letting the browser decide on the refresh rate (reqeustAnimationFrame) is the better way from the browsers perspective, in terms of trying to maintain a consistent framerate. Alternatively, skipping frames from requestAnimationFrame is also a reasonable solution. Because there is no way to reliably query the browser for the refresh rate requestAnimationFrame is targetting, which is dependent on the refresh rate of the current monitor, Unity makes the arguably naïve assumption of 60 Hz, because that’s still the most common monitor refresh rate. Unity will use requestAnimationFrame and skip frames if your target frame rate is evenly divisible from 60. For example, a target frame rate of 30 will use requestAnimationFrame and skip every other frame. A target frame rate of 15 will use requestAnimationFrame and render every 4th frame. Setting the target frame rate to any other value will use a timer instead of requestAnimationFrame for the rendering rate. While this gives you some more control, there is no way for the browser to guarantee to any reasonable degree that timing, because the browser will still composite the page at the rate it wants to, and the frame you just rendered may have to wait for that to be displayed. VSync options in Unity have no affect for the web platform because there is no way to know what the vsync rate is or wait for it from a browser. If setting the target frame rate is not working, as you seem to be saying, then that is a Unity bug.

Right, thanks. so does this mean that I can get it to only render every 4th frame by setting target frame rate 15? Ideally I’d like to have it updating very infrequently most of the time and then occasionally (on user input) have it update at 60 or whatever the browser wants?

It might be possible to change the target frame rate at runtime, switching between say 15 and -1. Perhaps change it to -1 if you get some input, and use a timer to change it back to 15 after some period if no input. But there would be no way to automatically do this, and I’ve never tested this to know if it would work or if I’m just making things up.

great, thanks I’ll give it a go and let you know how I get on :slight_smile: The main thing is that we have super heavy SSAO (which is essential for our use case) so even if I could get the post processing to not update every frame that would be amazing

You can’t update a screen space effect independently of rendering the scene, since the effect is dependent on what you rendered. So, unless turning off the effect for certain frames is an option, the only other option is to not render the frames as much as you can get away with, and target frame rate is the only real tool you have to control that.

1 Like

Thanks for the reply!
It does look like a bug to me:
I just made a new empty project with the same Unity version I first noticed this with (2021.3.18f1), adding a script to a GameObject in the scene setting Application.targetFrameRate = 30; in Start(), disabling build compression and Auto Graphics API and made a build. I also repeated the same process for a second new project in the currently latest 2022 LTS 3.5f1 version.
I checked both builds hosted on a GitLab Pages page on Windows 10/11 in Brave and Edge, using the Developer Tools’ “Frame Rendering Stats” overlay from the Console drawer’s Rendering tab, and I get significantly higher fps than the expected ~30, seemingly still trying to match the screen refresh rate (e.g. 230+ frames per second on a laptop with a 240 Hz screen).
I reported a bug with the 2022 project attached (Case IN-49893) and will link to it once it goes public.

1 Like

I just checked another build I’ve had online for close to a year where that method of capping the frame rate definitely worked in the past, so I’m wondering if some browser update broke that?

I’ll holler at people to look into it and get it fixed.

3 Likes

as always I’m going to hop on here and say: opening a bug report with a repro project would make fixing this a 1000 times faster :smile:

1 Like