From cec7c908cae115d252c63c82eb1217c403fffab3 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Sun, 6 Mar 2022 00:34:56 +0100 Subject: [PATCH] Add `get_screen_refresh_rate()` to OS This method can be used to get the refresh rate of a given screen. It is supported on Windows, macOS, Linux, Android and iOS (but not HTML5). --- core/bind/core_bind.cpp | 5 ++ core/bind/core_bind.h | 1 + core/os/os.h | 4 ++ doc/classes/OS.xml | 14 +++++ .../src/org/godotengine/godot/GodotIO.java | 8 +++ platform/android/java_godot_io_wrapper.cpp | 14 +++++ platform/android/java_godot_io_wrapper.h | 2 + platform/android/os_android.cpp | 4 ++ platform/android/os_android.h | 1 + platform/iphone/os_iphone.h | 1 + platform/iphone/os_iphone.mm | 4 ++ platform/osx/os_osx.h | 1 + platform/osx/os_osx.mm | 16 +++++ platform/windows/os_windows.cpp | 42 ++++++++++++- platform/windows/os_windows.h | 1 + platform/x11/os_x11.cpp | 59 +++++++++++++++++++ platform/x11/os_x11.h | 1 + 17 files changed, 177 insertions(+), 1 deletion(-) diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index e20988c9349..b76c36f33f8 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -311,6 +311,10 @@ float _OS::get_screen_max_scale() const { return OS::get_singleton()->get_screen_max_scale(); } +float _OS::get_screen_refresh_rate(int p_screen) const { + return OS::get_singleton()->get_screen_refresh_rate(); +} + Point2 _OS::get_window_position() const { return OS::get_singleton()->get_window_position(); } @@ -1267,6 +1271,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_screen_dpi", "screen"), &_OS::get_screen_dpi, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_screen_scale", "screen"), &_OS::get_screen_scale, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_screen_max_scale"), &_OS::get_screen_max_scale); + ClassDB::bind_method(D_METHOD("get_screen_refresh_rate", "screen"), &_OS::get_screen_refresh_rate, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_window_position"), &_OS::get_window_position); ClassDB::bind_method(D_METHOD("set_window_position", "position"), &_OS::set_window_position); ClassDB::bind_method(D_METHOD("get_window_size"), &_OS::get_window_size); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 2f1c9e32506..394d8807ba2 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -191,6 +191,7 @@ public: virtual int get_screen_dpi(int p_screen = -1) const; virtual float get_screen_scale(int p_screen = -1) const; virtual float get_screen_max_scale() const; + virtual float get_screen_refresh_rate(int p_screen = -1) const; virtual Point2 get_window_position() const; virtual void set_window_position(const Point2 &p_position); virtual Size2 get_max_window_size() const; diff --git a/core/os/os.h b/core/os/os.h index 27c29ccfebb..5209f40c2dc 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -209,6 +209,9 @@ public: virtual void open_midi_inputs(); virtual void close_midi_inputs(); + // Returned by get_screen_refresh_rate if the method fails. + const float SCREEN_REFRESH_RATE_FALLBACK = -1.0; + virtual int get_screen_count() const { return 1; } virtual int get_current_screen() const { return 0; } virtual void set_current_screen(int p_screen) {} @@ -217,6 +220,7 @@ public: virtual int get_screen_dpi(int p_screen = -1) const { return 72; } virtual float get_screen_scale(int p_screen = -1) const { return 1.0; } virtual float get_screen_max_scale() const { return 1.0; }; + virtual float get_screen_refresh_rate(int p_screen = -1) const { return SCREEN_REFRESH_RATE_FALLBACK; }; virtual Point2 get_window_position() const { return Vector2(); } virtual void set_window_position(const Point2 &p_position) {} virtual Size2 get_max_window_size() const { return Size2(); }; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 910eaf6ca8c..f12f1a4edc8 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -408,6 +408,20 @@ Returns the position of the specified screen by index. If [code]screen[/code] is [code]-1[/code] (the default value), the current screen will be used. + + + + + Returns the current refresh rate of the specified screen. If [code]screen[/code] is [code]-1[/code] (the default value), the current screen will be used. + [b]Note:[/b] Returns [code]-1.0[/code] if Godot fails to find the refresh rate for the specified screen. On HTML5, [method get_screen_refresh_rate] will always return [code]-1.0[/code] as there is no way to retrieve the refresh rate on that platform. + To fallback to a default refresh rate if the method fails, try: + [codeblock] + var refresh_rate = OS.get_screen_refresh_rate() + if refresh_rate < 0: + refresh_rate = 60.0 + [/codeblock] + + diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index d8bd39af74b..6ff47456880 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -226,6 +226,14 @@ public class GodotIO { return (int)(metrics.density * 160f); } + public double getScreenRefreshRate(double fallback) { + Display display = activity.getWindowManager().getDefaultDisplay(); + if (display != null) { + return display.getRefreshRate(); + } + return fallback; + } + public int[] getWindowSafeArea() { DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); Display display = activity.getWindowManager().getDefaultDisplay(); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 0c4d64b19e8..25765f714ea 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -53,6 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); + _get_screen_refresh_rate = p_env->GetMethodID(cls, "getScreenRefreshRate", "(D)D"); _get_window_safe_area = p_env->GetMethodID(cls, "getWindowSafeArea", "()[I"), _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V"); @@ -136,6 +137,19 @@ int GodotIOJavaWrapper::get_screen_dpi() { } } +float GodotIOJavaWrapper::get_screen_refresh_rate(float p_fallback) { + if (_get_screen_refresh_rate) { + JNIEnv *env = get_jni_env(); + if (env == nullptr) { + ERR_PRINT("An error occurred while trying to get screen refresh rate."); + return p_fallback; + } + return (float)env->CallDoubleMethod(godot_io_instance, _get_screen_refresh_rate, (double)p_fallback); + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return p_fallback; +} + void GodotIOJavaWrapper::get_window_safe_area(int (&p_rect_xywh)[4]) { if (_get_window_safe_area) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index 935c6664bc1..f21e7874936 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -51,6 +51,7 @@ private: jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; + jmethodID _get_screen_refresh_rate = 0; jmethodID _get_window_safe_area = 0; jmethodID _get_unique_id = 0; jmethodID _show_keyboard = 0; @@ -72,6 +73,7 @@ public: String get_model(); int get_screen_dpi(); void get_window_safe_area(int (&p_rect_xywh)[4]); + float get_screen_refresh_rate(float p_fallback); String get_unique_id(); bool has_vk(); void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 6d48dadedae..67194809234 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -444,6 +444,10 @@ int OS_Android::get_screen_dpi(int p_screen) const { return godot_io_java->get_screen_dpi(); } +float OS_Android::get_screen_refresh_rate(int p_screen) const { + return godot_io_java->get_screen_refresh_rate(OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK); +} + String OS_Android::get_data_path() const { return get_user_data_dir(); } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 6a6a529478a..cd88f1a9c81 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -154,6 +154,7 @@ public: virtual bool has_clipboard() const; virtual String get_model_name() const; virtual int get_screen_dpi(int p_screen = 0) const; + virtual float get_screen_refresh_rate(int p_screen = 0) const; virtual bool get_window_per_pixel_transparency_enabled() const { return transparency_enabled; } virtual void set_window_per_pixel_transparency_enabled(bool p_enabled) { ERR_FAIL_MSG("Setting per-pixel transparency is not supported at runtime, please set it in project settings instead."); } diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index ac6165e9598..c2fdd1bcd0b 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -130,6 +130,7 @@ public: virtual bool _check_internal_feature_support(const String &p_feature); virtual int get_screen_dpi(int p_screen = -1) const; + virtual float get_screen_refresh_rate(int p_screen = -1) const; void pencil_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 7c27aebcb5e..4936eed0e67 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -565,6 +565,10 @@ int OSIPhone::get_screen_dpi(int p_screen) const { } } +float OSIPhone::get_screen_refresh_rate(int p_screen) const { + return [UIScreen mainScreen].maximumFramesPerSecond; +} + Rect2 OSIPhone::get_window_safe_area() const { if (@available(iOS 11, *)) { UIEdgeInsets insets = UIEdgeInsetsZero; diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index e3bd7b93794..5b02153ce76 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -276,6 +276,7 @@ public: virtual int get_screen_dpi(int p_screen = -1) const; virtual float get_screen_scale(int p_screen = -1) const; virtual float get_screen_max_scale() const; + virtual float get_screen_refresh_rate(int p_screen = -1) const; virtual Point2 get_window_position() const; virtual void set_window_position(const Point2 &p_position); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index a662ba81a6e..9495099de78 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -2564,6 +2564,22 @@ int OS_OSX::get_screen_dpi(int p_screen) const { return 72; } +float OS_OSX::get_screen_refresh_rate(int p_screen) const { + if (p_screen < 0) { + p_screen = get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; + const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); + return (float)displayRefreshRate; + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK; +} + Size2 OS_OSX::get_screen_size(int p_screen) const { if (p_screen < 0) { p_screen = get_current_screen(); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 07acf5ee27e..26409cbc5d4 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1954,6 +1954,12 @@ typedef struct { int dpi; } EnumDpiData; +typedef struct { + int count; + int screen; + float rate; +} EnumRefreshRateData; + static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumDpiData *data = (EnumDpiData *)dwData; if (data->count == data->screen) { @@ -1964,12 +1970,46 @@ static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRE return TRUE; } +static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + EnumRefreshRateData *data = (EnumRefreshRateData *)dwData; + if (data->count == data->screen) { + MONITORINFOEXW minfo; + memset(&minfo, 0, sizeof(minfo)); + minfo.cbSize = sizeof(minfo); + GetMonitorInfoW(hMonitor, &minfo); + + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + + data->rate = dm.dmDisplayFrequency; + } + + data->count++; + return TRUE; +} + int OS_Windows::get_screen_dpi(int p_screen) const { - EnumDpiData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, 72 }; + EnumDpiData data = { + 0, + p_screen == -1 ? get_current_screen() : p_screen, + 72, + }; EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; } +float OS_Windows::get_screen_refresh_rate(int p_screen) const { + EnumRefreshRateData data = { + 0, + p_screen == -1 ? get_current_screen() : p_screen, + OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK, + }; + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); + return data.rate; +} + Point2 OS_Windows::get_window_position() const { if (minimized) { return last_pos; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index e05fc06292c..56d5f8b3d2d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -445,6 +445,7 @@ public: virtual Point2 get_screen_position(int p_screen = -1) const; virtual Size2 get_screen_size(int p_screen = -1) const; virtual int get_screen_dpi(int p_screen = -1) const; + virtual float get_screen_refresh_rate(int p_screen = -1) const; virtual Point2 get_window_position() const; virtual void set_window_position(const Point2 &p_position); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 479eb7c58b6..7127447f140 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -1323,6 +1323,65 @@ int OS_X11::get_screen_dpi(int p_screen) const { return 96; } +float OS_X11::get_screen_refresh_rate(int p_screen) const { + if (p_screen == -1) { + p_screen = get_current_screen(); + } + + //invalid screen? + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK); + + //Use xrandr to get screen refresh rate. + if (xrandr_ext_ok) { + XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, x11_window); + if (screen_info) { + RRMode current_mode = 0; + xrr_monitor_info *monitors = nullptr; + + if (xrr_get_monitors) { + int count = 0; + monitors = xrr_get_monitors(x11_display, x11_window, true, &count); + ERR_FAIL_INDEX_V(p_screen, count, OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK); + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK; + } + + bool found_active_mode = false; + for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting. + XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]); + if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue. + continue; + } + + if (monitor_info->mode != None) { + current_mode = monitor_info->mode; + found_active_mode = true; + break; + } + } + + if (found_active_mode) { + for (int mode = 0; mode < screen_info->nmode; mode++) { + XRRModeInfo m_info = screen_info->modes[mode]; + if (m_info.id == current_mode) { + // Snap to nearest 0.01 to stay consistent with other platforms. + return Math::stepify((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01); + } + } + } + + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred. + return OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK; + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK; + } + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return OS::get_singleton()->SCREEN_REFRESH_RATE_FALLBACK; +} + Point2 OS_X11::get_window_position() const { int x, y; Window child; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 908e5a61f69..bed55903060 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -296,6 +296,7 @@ public: virtual Point2 get_screen_position(int p_screen = -1) const; virtual Size2 get_screen_size(int p_screen = -1) const; virtual int get_screen_dpi(int p_screen = -1) const; + virtual float get_screen_refresh_rate(int p_screen = -1) const; virtual Point2 get_window_position() const; virtual void set_window_position(const Point2 &p_position); virtual Size2 get_window_size() const;