From 98f377d9d0c7666d169521fd5fb2c78c3f87a598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:21:01 +0300 Subject: [PATCH] Use system timer/wait functions for frame delay when screen reader is active. --- core/os/os.cpp | 20 +++++++++++++++++++- core/os/os.h | 3 ++- main/main.cpp | 6 ++---- platform/macos/os_macos.h | 3 +++ platform/macos/os_macos.mm | 27 +++++++++++++++++++++++++-- platform/web/os_web.cpp | 4 ++-- platform/web/os_web.h | 2 +- platform/windows/os_windows.cpp | 16 +++++++++++++++- platform/windows/os_windows.h | 2 +- 9 files changed, 70 insertions(+), 13 deletions(-) diff --git a/core/os/os.cpp b/core/os/os.cpp index 29e49f8f65f..51bf30ac7eb 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -658,7 +658,25 @@ void OS::close_midi_inputs() { } } -void OS::add_frame_delay(bool p_can_draw) { +uint64_t OS::get_frame_delay(bool p_can_draw) const { + const uint32_t frame_delay = Engine::get_singleton()->get_frame_delay(); + + // Add a dynamic frame delay to decrease CPU/GPU usage. This takes the + // previous frame time into account for a smoother result. + uint64_t dynamic_delay = 0; + if (is_in_low_processor_usage_mode() || !p_can_draw) { + dynamic_delay = get_low_processor_usage_mode_sleep_usec(); + } + const int max_fps = Engine::get_singleton()->get_max_fps(); + if (max_fps > 0 && !Engine::get_singleton()->is_editor_hint()) { + // Override the low processor usage mode sleep delay if the target FPS is lower. + dynamic_delay = MAX(dynamic_delay, (uint64_t)(1000000 / max_fps)); + } + + return frame_delay + dynamic_delay; +} + +void OS::add_frame_delay(bool p_can_draw, bool p_wake_for_events) { const uint32_t frame_delay = Engine::get_singleton()->get_frame_delay(); if (frame_delay) { // Add fixed frame delay to decrease CPU/GPU usage. This doesn't take diff --git a/core/os/os.h b/core/os/os.h index d6778c990a3..530859acdcb 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -247,7 +247,8 @@ public: virtual double get_unix_time() const; virtual void delay_usec(uint32_t p_usec) const = 0; - virtual void add_frame_delay(bool p_can_draw); + virtual void add_frame_delay(bool p_can_draw, bool p_wake_for_events); + virtual uint64_t get_frame_delay(bool p_can_draw) const; virtual uint64_t get_ticks_usec() const = 0; uint64_t get_ticks_msec() const; diff --git a/main/main.cpp b/main/main.cpp index 310494724ad..1f637aab0ec 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4855,11 +4855,9 @@ bool Main::iteration() { } SceneTree *scene_tree = SceneTree::get_singleton(); - bool skip_delay = scene_tree && scene_tree->is_accessibility_enabled(); + bool wake_for_events = scene_tree && scene_tree->is_accessibility_enabled(); - if (!skip_delay) { - OS::get_singleton()->add_frame_delay(DisplayServer::get_singleton()->window_can_draw()); - } + OS::get_singleton()->add_frame_delay(DisplayServer::get_singleton()->window_can_draw(), wake_for_events); #ifdef TOOLS_ENABLED if (auto_build_solutions) { diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index dac11f26ebd..0618d0fafbd 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -64,6 +64,7 @@ protected: JoypadApple *joypad_apple = nullptr; MainLoop *main_loop = nullptr; + CFRunLoopTimerRef wait_timer = nil; virtual void initialize_core() override; virtual void initialize() override; @@ -75,6 +76,8 @@ protected: virtual void delete_main_loop() override; public: + virtual void add_frame_delay(bool p_can_draw, bool p_wake_for_events) override; + virtual void set_cmdline_platform_args(const List &p_args); virtual List get_cmdline_platform_args() const override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index bff0ffe780c..2c4c79e1b54 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -49,6 +49,28 @@ #include #include +void OS_MacOS::add_frame_delay(bool p_can_draw, bool p_wake_for_events) { + if (p_wake_for_events) { + uint64_t delay = get_frame_delay(p_can_draw); + if (delay == 0) { + return; + } + if (wait_timer) { + CFRunLoopTimerInvalidate(wait_timer); + CFRelease(wait_timer); + } + wait_timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + (double(delay) / 1000000.0), 0, 0, 0, + ^(CFRunLoopTimerRef timer) { + CFRunLoopTimerInvalidate(wait_timer); + CFRelease(wait_timer); + wait_timer = nil; + }); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), wait_timer, kCFRunLoopCommonModes); + return; + } + OS_Unix::add_frame_delay(p_can_draw, p_wake_for_events); +} + void OS_MacOS::initialize() { crash_handler.initialize(); @@ -995,8 +1017,9 @@ void OS_MacOS_NSApp::start_main() { ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); } } - - CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. + if (wait_timer == nil) { + CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. + } }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); return; diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index bb05ce1fe90..57d03418418 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -180,9 +180,9 @@ String OS_Web::get_name() const { return "Web"; } -void OS_Web::add_frame_delay(bool p_can_draw) { +void OS_Web::add_frame_delay(bool p_can_draw, bool p_wake_for_events) { #ifndef PROXY_TO_PTHREAD_ENABLED - OS::add_frame_delay(p_can_draw); + OS::add_frame_delay(p_can_draw, p_wake_for_events); #endif } diff --git a/platform/web/os_web.h b/platform/web/os_web.h index 725eda3621d..3f64b5a195d 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -99,7 +99,7 @@ public: // Override default OS implementation which would block the main thread with delay_usec. // Implemented in web_main.cpp loop callback instead. - void add_frame_delay(bool p_can_draw) override; + void add_frame_delay(bool p_can_draw, bool p_wake_for_events) override; void vibrate_handheld(int p_duration_ms, float p_amplitude) override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 14d34fb1178..f871deabd8f 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2494,7 +2494,21 @@ String OS_Windows::get_system_ca_certificates() { return certs; } -void OS_Windows::add_frame_delay(bool p_can_draw) { +void OS_Windows::add_frame_delay(bool p_can_draw, bool p_wake_for_events) { + if (p_wake_for_events) { + uint64_t delay = get_frame_delay(p_can_draw); + if (delay == 0) { + return; + } + + DisplayServer *ds = DisplayServer::get_singleton(); + DisplayServerWindows *ds_win = Object::cast_to(ds); + if (ds_win) { + MsgWaitForMultipleObjects(0, nullptr, false, Math::floor(double(delay) / 1000.0), QS_ALLINPUT); + return; + } + } + const uint32_t frame_delay = Engine::get_singleton()->get_frame_delay(); if (frame_delay) { // Add fixed frame delay to decrease CPU/GPU usage. This doesn't take diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 1f762c06296..11f86999292 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -193,7 +193,7 @@ public: virtual Error set_cwd(const String &p_cwd) override; - virtual void add_frame_delay(bool p_can_draw) override; + virtual void add_frame_delay(bool p_can_draw, bool p_wake_for_events) override; virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override;