I wanted to create this thread to discuss the issue and the fix for Unity Issue Tracker - [Windows] The returned screen refresh rate is rounded down when calling Screen.currentResolution.
Let me give you some background information first. Refresh rate in Unity’s scripting API has traditionally been an integer: Unity - Scripting API: Resolution.refreshRate
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: Unity - Scripting API: Resolution
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?