Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only.

    Please, do not make any changes to your username or email addresses at id.unity.com during this transition time.

    On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live.


    Read our full announcement for more information and let us know if you have any questions.

Refresh rate rounding on Windows

Discussion in 'Windows' started by Tautvydas-Zilys, Jul 13, 2022.

  1. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    I wanted to create this thread to discuss the issue and the fix for https://issuetracker.unity3d.com/is...n-when-calling-screen-dot-currentresolution-1.

    Let me give you some background information first. Refresh rate in Unity's scripting API has traditionally been an integer: https://docs.unity3d.com/2020.3/Documentation/ScriptReference/Resolution-refreshRate.html

    However, monitor's don't actually refresh in integer refresh rates. Instead, they use fractions to represent refresh rates, in terms of numerator and denominator. So, instead of having a 60 Hz monitor, you may have a "60000 / 1001 Hz" (59.94 Hz) or "59966 / 1000 (59.96 Hz)" monitor. So, we have the first challenge: we need to represent a non-integer refresh rate as integer when exposing it in Unity's API.

    There are three possible solutions: round down, round up, or round to nearest integer. And this is where we screwed up, but not in a way you think.

    You see, we have several APIs that return monitor's resolution. Among those, are Screen.resolutions that returns an array of supported monitor display modes. We also have Screen.currentResolution, which returns the current display mode if you're running in windowed mode (and something else when running in fullscreen due to a decision made many years ago). You would reasonably expect that you could enumerate the supported display modes returned by Screen.resolutions, then call "Screen.SetResolution(res.width, res.height, res.refreshRate, FullScreenMode.ExclusiveFullScreen)" and then Screen.currentResolution will return the refresh rate that you set.

    However, that wasn't the case, and that was really the main issue that I tried to address when fixing this bug. The cause for this was the fact that when we try to find the current resolution, we used the EnumDisplaySettingsW Windows API. This API already returns the refresh rate as integer with whatever rounding rules it uses (I actually am not quite sure how it does the rounding, as some monitors return 59 Hz and some return 60 Hz even though both are 59.94 Hz in reality). However, when finding the results to return for "Screen.resolutions", we use IDXGIOutput::GetDisplayModeList instead, which gives us actual refresh rates as fractional values, which we always rounded to "nearest integer". This resulted in cases where you ask Unity for supported refresh rates, it tells you 60 Hz is supported, you set the resolution to 60 Hz and then Screen.currentResolution says you're running at 59 Hz.

    Now, it's been a while since we realized that refresh rates are integers are not great. When introducing the new MoveWindow/DisplayLayout API in Unity 2021.2, we introduced the new RefreshRate struct which accurately represents the refresh rate. However, at the time we did not replace the original refresh rate field in the Resolution struct due to backwards compatibility concerns: we weren't sure how many projects we break so we needed to build a deprecation plan, which missed the 2021.2 feature cut off. However, I managed to get it into Unity 2022.2: https://docs.unity3d.com/2022.2/Documentation/ScriptReference/Resolution.html

    The fix for this original issue is two fold: for Unity 2022.2, there wasn't really a fix since refresh rate isn't rounded anymore. For Unity 2020.3, 2021.3 and 2022.1, I made us use the QueryDisplayConfig Windows API instead of the old EnumDisplaySettingsW for determining Screen.currentResolution, which makes enables us to do the rounding ourselves and make it identical to the rounding used for Screen.resolutions.

    @atomicjoe you commented on the issue tracker list that this issue is important to get right for video. Can you elaborate why? I believe that video playback has nothing to do with exclusive fullscreen display mode since we don't adjust it when playing video. Do you do that in your project?
     
  2. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    I missed this thread and didn't receive any alert in the forums, that's weird. I only found it because I was checking the release notes of 2021.3.6f1

    Anyway, to the issue:

    PAL video is recorded at 25 frames per second and Cinema at 24 frames per second, but contrary to popular believe, NTSC video is not recorded and played at 30 fps but at 29.97 fps or 59.94 frames per second.
    This is because the NTSC standard is very old and originated on black and white TV, which actually was at integer 30 fps (or rather, 60 half frames per second interlaced) but when color was introduced, they had to make place for it, so they squeezed the chroma component in the same signal and had to slow down the frame rate to compensate. Hence why this odd number.

    Now, the problem is that, currently, smartphones, professional camcorders and professional digital cameras used for video production usually record at those specific frame rates: NOT 30fps or 60fps, but 29.97 and 59.94 fps.
    (although some digital cameras let you choose actual integer 24, 30 and 60 frames per second, but it's NOT the standard and when they do, they specify it. When it's not specified, it's usually recorded at 29.97 , 59.94 and 23.97 instead of 24)
    (23.97 frames per second was popularized in the DSLR era for recording Cinema-like video that would translate nicely to 29.97 frames per second.)

    You may not have noticed, but if you play a 59.94 frames per second video on a 60 frames per second display (a PC monitor) you WILL have some stuttering in the motion. (audio isn't affected. Or maybe it is but is not noticeable).
    So much so that consoles and Android video players like the Nvidia Shield TV let you choose the refresh rate, but they will default and recommend 59.94 Hz (NOT 60 Hz)

    Nowadays, digital TVs are able to set the screen to 60 Hz, but, again, it's NOT the default. In fact, even when the TV says it's in 60Hz, it's usually lying: it's actually 59.94Hz for legacy compatibility and standards.
    Even the new HDTV standard ATSC uses 59.94 Hz as standard, not 60Hz.

    Not being able to tell the difference between two identical resolutions but one in 59.94Hz and the other in 60Hz can be a problem on consoles or TV enabled devices and video will slightly stutter.

    This is becoming a problem now that the video and the PC worlds are merging.
    I suggest you talk to the Unity Video Player team developers ( @The_Island ) about this, they will be able to tell you if this is a problem for the Unity video player or if they are managing that on their own or what. (because there are plenty of other issues too, like RGB levels not being the same for video than PC in general...)

    Another example of standards clashing is for audio: there are still problems on some PCs when setting Windows to 48000 Hz instead of 44000 Hz or vice-versa: ugly crackling or slowing down. And it's for the same reasons: 44Khz comes from the audio production world and 48Khz comes from the video production world...
     
    Last edited: Jul 27, 2022
    PeachyPixels likes this.
  3. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    In the worst case, if they aren't compensating for the difference between 59.94 and 60 fps (because they check the screen API and get a value of 60 for both, for example) the video could de-synchronize with the audio, stutter or both.

    Video is more complicated than it seems LOL
     
  4. The_Island

    The_Island

    Unity Technologies

    Joined:
    Jun 1, 2021
    Posts:
    502
    The VideoPlayer is not using the screen/display refresh rate in any way. It has its own timer, and whenever the frame's timestamp >= to the timer, we blit the image inside the texture.

    We still sometimes have issues with some fps on some platforms because of multiple limitations. Some foundations don't always provide all the info needed and Unity time representation is in float, which is unsuitable for video and audio. Also, we try not to use the fps because some video has a variable frame rate and can cause significant issues if we blindly follow it. But we are sometimes forced to use it or even need to deduce it. It is one of the reasons we want to move away from these dependencies. Generally, I would recommend having a constant frame rate for stability but the actual frame rate doesn't matter too much.

    @atomicjoe, we do support video frame rate like 29.97, 59.94 and such. In fact, the majority of videos we test with are 23.97. If you have any problems, don't hesitate to reach out on the forum.
     
    atomicjoe likes this.
  5. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869
    All is good then, nothing more to add. :)
     
  6. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Hi @Tautvydas-Zilys

    I've just upgraded to 2022 and stumbled across this.

    To keep things simple, my game settings allow for an Optimal refresh rate (i.e. 60fps on a 60hz display) or Reduced (i.e. 30fps on a 60hz display) like so...

    Code (CSharp):
    1. int refreshRate = Screen.currentResolution.refreshRate;
    2.  
    3. Application.targetFrameRate = refreshRate;
    4. Application.targetFrameRate = refreshRate / 2;
    This works well for Android & iOS and for Windows vSyncCount 1&2 is used.

    The trouble is this now causes an incompatibility with Application.targetFrameRate so as a temporary workaround I am rounding up the refresh rate like so...

    Code (CSharp):
    1. int refreshRate = (int)Math.Ceiling(Screen.currentResolution.refreshRateRatio.value);
    2.  
    3. Application.targetFrameRate = refreshRate;
    4. Application.targetFrameRate = refreshRate / 2;
    In theory that should give the same result, but is the correct approach to use Screen.SetResolution(...) and create my own RefreshRate object for the Reduced setting?

    Maybe this isn't an issue as long as Windows continues to use vSyncCount?

    Any advice would be greatly appreciated.
     
    Last edited: Jul 19, 2023
  7. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    Application.targetFrameRate gets ignored when vSync is on, so you don't have to worry about this on Windows. For Android/iOS, you are pretty much set, except I'd do a small tweak: round the refreshRateRatio.value, rather than ceil it to avoid floating point shenanigans. Currently (as of Unity 2022.3 and 2023.2), both Android & iOS implementations values should come down to whole integer values (but are subject to change).
     
    PeachyPixels likes this.
  8. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Great! Thanks @Tautvydas-Zilys

    Yes that was my understanding.

    Thanks for confirming. Am assuming you mean Math.Round (so it'll round up or down, rather than always up)

    I had assumed Android & iOS devices all reported whole numbers. I've certainly not heard anything to the contrary. If this changes in the future, it sounds like it'll require no to minimal changes thankfully.
     
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    Correct.
     
    PeachyPixels likes this.
  10. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    I'm using 2022.LTS and at some point in the last few LTS releases (I believe) this is now broken in the Editor (Windows 10)

    I've a dual monitor setup (with both set to 1920\1080\60hz) and Screen.currentResolution.refreshRateRatio now returns...

    Code (CSharp):
    1. Screen.currentResolution.refreshRateRatio
    2. "NaN"
    3.     denominator: 0
    4.     numerator: 0
    5.     value: NaN
    Which ultimately breaks my own code with regards to an in-game performance option.

    I haven't checked app builds yet (on UWP, Android & iOS)

    UPDATE: Disconnecting the second monitor (and changing the Windows projection setting to a single monitor) still reports a NaN, so suspect it's a platform issue.
     
    Last edited: Jan 5, 2024
  11. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    A simple workaround if anyone encounters this...

    Code (CSharp):
    1.     int refreshRate = 60;
    2.     if ((!Double.IsNaN(Screen.currentResolution.refreshRateRatio.value)) && (Screen.currentResolution.refreshRateRatio.value > 0.0))
    3.       refreshRate = (int)Math.Round(Screen.currentResolution.refreshRateRatio.value);
    Basically, default to 60hz if it's invalid.
     
    Last edited: Jan 5, 2024
  12. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    What does Windows report in display settings as the refresh rate? upload_2024-1-4_8-39-54.png

    What exact version of the LTS are you seeing this on? And which version does it work fine on?
     
  13. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Hmmm, something very odd going on here.

    Here's my setup...

    Unity 2022.3.16f1
    Windows 10 Pro (22H2)
    Laptop (Internal Display, Display 1)
    Monitor (Display 2)
    Monitor (Display 3)

    So when I checked the settings (which have always shown Display 3, my main display) it's now showing Internal Display (i.e., Display 1). But only Display 3 is showing the desktop (set via Win-P projection settings)

    So I changed the Windows setting back to Display 3 (which correctly reported 1920\1080\60) applied the change and closed the settings window. Upon opening it again, it's back to Internal Display.

    And no matter what I do, Unity is always reporting NaN. Even if I physically disconnect the laptop from the monitors and use the Internal Display only.

    I updated to Windows 22H2 a month or two ago. Maybe it's a Windows issue that I didn't notice in Unity until now (which just coincides with me updating the LTS version)
     
    Last edited: Jan 5, 2024
  14. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    UPDATE:

    I'm in the process of updating two games from Unity 2020.3.40 to Unity 2022.3.16

    I've upgraded one to 2022 and it's reporting NaN. The second game running on 2020 is reporting the correct 60, but this pre-dates the refresh rate change from int to double.

    So maybe it's a combination of the change to double and Windows 22H2.
     
  15. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    It's more likely it's due to the fact that we stopped defaulting to 60 Hz if we cannot determine the refresh rate during that time period.

    We should be able to address it, but I need more details. What does Windows show as the refresh rate? Does this reproduce if duplication is disabled?
     
    PeachyPixels likes this.
  16. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Advanced Settings

    Scenario 1 (Laptop Disconnected from Dock)

    Internal Display (Main Display)...

    Connected to Intel HD Graphics (5000)
    Desktop Resolution: 2160 x 1440
    Active Signal Resolution: 2160 x 1440
    Refresh Rate: 60.015 Hz

    Unity Reports NaN

    Scenario 2 (Laptop Connected to Dock)

    Internal Display...

    Display Isn't Active

    Display 3 (Main Display)...

    Connected to Intel HD Graphics (5000)
    Desktop Resolution: 1920 x 1080
    Active Signal Resolution: 1920 x 1080
    Refresh Rate: 60.000 Hz

    Unity Reports NaN

    Display Driver

    Intel: 20.19.15.4568

    So Unity is unable to determine the refresh rate, whether using the internal display only or extending\duplicating to a second display.

    When the original Unity change was released, it definitely worked. I remember changing the code, testing & seeing refresh rate reporting 60.

    Maybe that was internally defaulted by Unity (if it was unable to determine the rate from the OS) but this logic has since been removed?

    I hope this helps.
     
  17. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    Thanks. We will try reproducing it. I might come back and ask you for more info if needed!
     
    PeachyPixels likes this.
  18. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Quick update... I just tested the 2022.3.16 UWP build and it's reporting 60Hz (albeit rounded) so it looks like this might be an editor only issue.
     
  19. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,756
    Can you check Windows Standalone player? UWP uses a different code path.
     
  20. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    The game in question isn't geared up for standalone Windows builds, but I'll put together a quick test app and report back.
     
  21. PeachyPixels

    PeachyPixels

    Joined:
    Feb 17, 2018
    Posts:
    716
    Ok, the Win64 standalone test app (using the exact same hardware I mentioned above) reports...

    Internal Display (Laptop Disconnected from Dock) : 60.0148
    Display 3 (Laptop Connected to Dock) : 60

    So looking like the NaN issue might be editor only.