From 9d3b1904651321f44b8f801bc0452390afc16ffc Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 26 Jul 2022 22:04:18 +0100 Subject: [PATCH 1/4] Rework multi-core vsync --- src/core/hle/service/nvflinger/nvflinger.cpp | 42 ++++++++++++-------- src/core/hle/service/nvflinger/nvflinger.h | 5 ++- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 5574269eb2..9b382bf566 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) { Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - s64 delay = 0; + while (!stop_token.stop_requested()) { + vsync_signal.wait(false); + vsync_signal.store(false); + guard->lock(); - const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); + Compose(); - const auto ticks = GetNextTicks(); - const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count(); - const s64 time_passed = time_end - time_start; - const s64 next_time = std::max(0, ticks - time_passed - delay); + guard->unlock(); - if (next_time > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds{next_time}); - } - delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time; } } @@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr guard = std::make_shared(); // Schedule the screen composition events - composition_event = Core::Timing::CreateEvent( + multi_composition_event = Core::Timing::CreateEvent( + "ScreenComposition", + [this](std::uintptr_t, s64 time, + std::chrono::nanoseconds ns_late) -> std::optional { + vsync_signal.store(true); + vsync_signal.notify_all(); + return std::chrono::nanoseconds(GetNextTicks()); + }); + + single_composition_event = Core::Timing::CreateEvent( "ScreenComposition", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) -> std::optional { const auto lock_guard = Lock(); Compose(); - return std::max(std::chrono::nanoseconds::zero(), - std::chrono::nanoseconds(GetNextTicks()) - ns_late); + return std::chrono::nanoseconds(GetNextTicks()); }); if (system.IsMulticore()) { + system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); } else { - system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); + system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); } } NVFlinger::~NVFlinger() { - if (!system.IsMulticore()) { - system.CoreTiming().UnscheduleEvent(composition_event, 0); + if (system.IsMulticore()) { + system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); + vsync_thread.request_stop(); + vsync_signal.store(true); + vsync_signal.notify_all(); + } else { + system.CoreTiming().UnscheduleEvent(single_composition_event, {}); } for (auto& display : displays) { diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 4775597cce..044ac6ac8d 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -126,12 +126,15 @@ private: u32 swap_interval = 1; /// Event that handles screen composition. - std::shared_ptr composition_event; + std::shared_ptr multi_composition_event; + std::shared_ptr single_composition_event; std::shared_ptr guard; Core::System& system; + std::atomic vsync_signal; + std::jthread vsync_thread; KernelHelpers::ServiceContext service_context; From 83a24ad638a2c3ff628fe109391586be9bd3a45d Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Wed, 27 Jul 2022 22:31:41 +0100 Subject: [PATCH 2/4] Make coretiming waiting more accurate --- src/common/thread.h | 4 ++++ src/core/core_timing.cpp | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/common/thread.h b/src/common/thread.h index 1552f58e0f..e17a7850fe 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -54,6 +54,10 @@ public: is_set = false; } + [[nodiscard]] bool IsSet() { + return is_set; + } + private: std::condition_variable condvar; std::mutex mutex; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b0..b45c1b9182 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -243,17 +243,17 @@ std::optional CoreTiming::Advance() { basic_lock.lock(); if (evt.reschedule_time != 0) { - // If this event was scheduled into a pause, its time now is going to be way behind. - // Re-set this event to continue from the end of the pause. - auto next_time{evt.time + evt.reschedule_time}; - if (evt.time < pause_end_time) { - next_time = pause_end_time + evt.reschedule_time; - } - const auto next_schedule_time{new_schedule_time.has_value() ? new_schedule_time.value().count() : evt.reschedule_time}; + // If this event was scheduled into a pause, its time now is going to be way behind. + // Re-set this event to continue from the end of the pause. + auto next_time{evt.time + next_schedule_time}; + if (evt.time < pause_end_time) { + next_time = pause_end_time + next_schedule_time; + } + event_queue.emplace_back( Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); @@ -264,8 +264,7 @@ std::optional CoreTiming::Advance() { } if (!event_queue.empty()) { - const s64 next_time = event_queue.front().time - global_timer; - return next_time; + return event_queue.front().time; } else { return std::nullopt; } @@ -278,11 +277,28 @@ void CoreTiming::ThreadLoop() { paused_set = false; const auto next_time = Advance(); if (next_time) { - if (*next_time > 0) { - std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); - event.WaitFor(next_time_ns); + // There are more events left in the queue, sleep until the next event. + const auto diff_ns{*next_time - GetGlobalTimeNs().count()}; + if (diff_ns > 0) { + // Only try to sleep if the remaining time is >= 1ms. Take off 500 microseconds + // from the target time to account for possible over-sleeping, and spin the + // remaining. + const auto sleep_time_ns{diff_ns - 500LL * 1'000LL}; + const auto sleep_time_ms{sleep_time_ns / 1'000'000LL}; + if (sleep_time_ms >= 1) { + event.WaitFor(std::chrono::nanoseconds(sleep_time_ns)); + } + + const auto end_time{std::chrono::nanoseconds(*next_time)}; + while (!paused && !event.IsSet() && GetGlobalTimeNs() < end_time) { + } + + if (event.IsSet()) { + event.Reset(); + } } } else { + // Queue is empty, wait until another event is scheduled and signals us to continue. wait_set = true; event.Wait(); } From 658e1ee4267f0475445c7152560783444fcda7d2 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Tue, 2 Aug 2022 05:28:31 +0100 Subject: [PATCH 3/4] Add missing looping event schedule signal --- src/core/core_timing.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index b45c1b9182..a75bfea60e 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -143,13 +143,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::chrono::nanoseconds resched_time, const std::shared_ptr& event_type, std::uintptr_t user_data, bool absolute_time) { - std::scoped_lock scope{basic_lock}; - const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; + { + std::scoped_lock scope{basic_lock}; + const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; - event_queue.emplace_back( - Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); + event_queue.emplace_back( + Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + } + + event.Set(); } void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, From 606cdb17d3b8f3b4898c1f0a87691058074ad11a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 1 Aug 2022 23:34:34 -0400 Subject: [PATCH 4/4] core_timing: Sleep in discrete intervals, yield during spin --- src/core/core_timing.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a75bfea60e..8d1ee3b516 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -281,20 +281,21 @@ void CoreTiming::ThreadLoop() { paused_set = false; const auto next_time = Advance(); if (next_time) { - // There are more events left in the queue, sleep until the next event. - const auto diff_ns{*next_time - GetGlobalTimeNs().count()}; - if (diff_ns > 0) { - // Only try to sleep if the remaining time is >= 1ms. Take off 500 microseconds - // from the target time to account for possible over-sleeping, and spin the - // remaining. - const auto sleep_time_ns{diff_ns - 500LL * 1'000LL}; - const auto sleep_time_ms{sleep_time_ns / 1'000'000LL}; - if (sleep_time_ms >= 1) { - event.WaitFor(std::chrono::nanoseconds(sleep_time_ns)); + // There are more events left in the queue, wait until the next event. + const auto wait_time = *next_time - GetGlobalTimeNs().count(); + if (wait_time > 0) { + // Assume a timer resolution of 1ms. + static constexpr s64 TimerResolutionNS = 1000000; + + // Sleep in discrete intervals of the timer resolution, and spin the rest. + const auto sleep_time = wait_time - (wait_time % TimerResolutionNS); + if (sleep_time > 0) { + event.WaitFor(std::chrono::nanoseconds(sleep_time)); } - const auto end_time{std::chrono::nanoseconds(*next_time)}; - while (!paused && !event.IsSet() && GetGlobalTimeNs() < end_time) { + while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { + // Yield to reduce thread starvation. + std::this_thread::yield(); } if (event.IsSet()) {