diff options
| author | 2022-09-17 11:10:54 -0700 | |
|---|---|---|
| committer | 2022-09-17 11:10:54 -0700 | |
| commit | 9c32f29af18f5412ea4ba9f15fe0da3805e0d858 (patch) | |
| tree | 63f5b2a5b5ad49179067d01e34c11fb2a7510bd8 | |
| parent | Merge pull request #8914 from lioncash/audio-const (diff) | |
| parent | core_timing: Sleep in discrete intervals, yield during spin (diff) | |
| download | yuzu-9c32f29af18f5412ea4ba9f15fe0da3805e0d858.tar.gz yuzu-9c32f29af18f5412ea4ba9f15fe0da3805e0d858.tar.xz yuzu-9c32f29af18f5412ea4ba9f15fe0da3805e0d858.zip | |
Merge pull request #8650 from Kelebek1/vsync
[Coretiming/NVNFlinger] Improve multi-core vsync timing, and core timing accuracy
Diffstat (limited to '')
| -rw-r--r-- | src/common/thread.h | 4 | ||||
| -rw-r--r-- | src/core/core_timing.cpp | 53 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/nvflinger.cpp | 42 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/nvflinger.h | 5 |
4 files changed, 71 insertions, 33 deletions
diff --git a/src/common/thread.h b/src/common/thread.h index 1552f58e0..e17a7850f 100644 --- a/src/common/thread.h +++ b/src/common/thread.h | |||
| @@ -54,6 +54,10 @@ public: | |||
| 54 | is_set = false; | 54 | is_set = false; |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | [[nodiscard]] bool IsSet() { | ||
| 58 | return is_set; | ||
| 59 | } | ||
| 60 | |||
| 57 | private: | 61 | private: |
| 58 | std::condition_variable condvar; | 62 | std::condition_variable condvar; |
| 59 | std::mutex mutex; | 63 | std::mutex mutex; |
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 5375a5d59..f6c4567ba 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp | |||
| @@ -134,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, | |||
| 134 | std::chrono::nanoseconds resched_time, | 134 | std::chrono::nanoseconds resched_time, |
| 135 | const std::shared_ptr<EventType>& event_type, | 135 | const std::shared_ptr<EventType>& event_type, |
| 136 | std::uintptr_t user_data, bool absolute_time) { | 136 | std::uintptr_t user_data, bool absolute_time) { |
| 137 | std::scoped_lock scope{basic_lock}; | 137 | { |
| 138 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | 138 | std::scoped_lock scope{basic_lock}; |
| 139 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | ||
| 140 | |||
| 141 | event_queue.emplace_back( | ||
| 142 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | ||
| 139 | 143 | ||
| 140 | event_queue.emplace_back( | 144 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| 141 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | 145 | } |
| 142 | 146 | ||
| 143 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 147 | event.Set(); |
| 144 | } | 148 | } |
| 145 | 149 | ||
| 146 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, | 150 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, |
| @@ -229,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 229 | basic_lock.lock(); | 233 | basic_lock.lock(); |
| 230 | 234 | ||
| 231 | if (evt.reschedule_time != 0) { | 235 | if (evt.reschedule_time != 0) { |
| 236 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 237 | ? new_schedule_time.value().count() | ||
| 238 | : evt.reschedule_time}; | ||
| 239 | |||
| 232 | // If this event was scheduled into a pause, its time now is going to be way behind. | 240 | // If this event was scheduled into a pause, its time now is going to be way behind. |
| 233 | // Re-set this event to continue from the end of the pause. | 241 | // Re-set this event to continue from the end of the pause. |
| 234 | auto next_time{evt.time + evt.reschedule_time}; | 242 | auto next_time{evt.time + next_schedule_time}; |
| 235 | if (evt.time < pause_end_time) { | 243 | if (evt.time < pause_end_time) { |
| 236 | next_time = pause_end_time + evt.reschedule_time; | 244 | next_time = pause_end_time + next_schedule_time; |
| 237 | } | 245 | } |
| 238 | 246 | ||
| 239 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 240 | ? new_schedule_time.value().count() | ||
| 241 | : evt.reschedule_time}; | ||
| 242 | |||
| 243 | event_queue.emplace_back( | 247 | event_queue.emplace_back( |
| 244 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); | 248 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); |
| 245 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 249 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| @@ -250,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 250 | } | 254 | } |
| 251 | 255 | ||
| 252 | if (!event_queue.empty()) { | 256 | if (!event_queue.empty()) { |
| 253 | const s64 next_time = event_queue.front().time - global_timer; | 257 | return event_queue.front().time; |
| 254 | return next_time; | ||
| 255 | } else { | 258 | } else { |
| 256 | return std::nullopt; | 259 | return std::nullopt; |
| 257 | } | 260 | } |
| @@ -264,11 +267,29 @@ void CoreTiming::ThreadLoop() { | |||
| 264 | paused_set = false; | 267 | paused_set = false; |
| 265 | const auto next_time = Advance(); | 268 | const auto next_time = Advance(); |
| 266 | if (next_time) { | 269 | if (next_time) { |
| 267 | if (*next_time > 0) { | 270 | // There are more events left in the queue, wait until the next event. |
| 268 | std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); | 271 | const auto wait_time = *next_time - GetGlobalTimeNs().count(); |
| 269 | event.WaitFor(next_time_ns); | 272 | if (wait_time > 0) { |
| 273 | // Assume a timer resolution of 1ms. | ||
| 274 | static constexpr s64 TimerResolutionNS = 1000000; | ||
| 275 | |||
| 276 | // Sleep in discrete intervals of the timer resolution, and spin the rest. | ||
| 277 | const auto sleep_time = wait_time - (wait_time % TimerResolutionNS); | ||
| 278 | if (sleep_time > 0) { | ||
| 279 | event.WaitFor(std::chrono::nanoseconds(sleep_time)); | ||
| 280 | } | ||
| 281 | |||
| 282 | while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { | ||
| 283 | // Yield to reduce thread starvation. | ||
| 284 | std::this_thread::yield(); | ||
| 285 | } | ||
| 286 | |||
| 287 | if (event.IsSet()) { | ||
| 288 | event.Reset(); | ||
| 289 | } | ||
| 270 | } | 290 | } |
| 271 | } else { | 291 | } else { |
| 292 | // Queue is empty, wait until another event is scheduled and signals us to continue. | ||
| 272 | wait_set = true; | 293 | wait_set = true; |
| 273 | event.Wait(); | 294 | event.Wait(); |
| 274 | } | 295 | } |
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 5574269eb..9b382bf56 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) { | |||
| 38 | 38 | ||
| 39 | Common::SetCurrentThreadName(name.c_str()); | 39 | Common::SetCurrentThreadName(name.c_str()); |
| 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); |
| 41 | s64 delay = 0; | 41 | |
| 42 | while (!stop_token.stop_requested()) { | 42 | while (!stop_token.stop_requested()) { |
| 43 | vsync_signal.wait(false); | ||
| 44 | vsync_signal.store(false); | ||
| 45 | |||
| 43 | guard->lock(); | 46 | guard->lock(); |
| 44 | const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); | 47 | |
| 45 | Compose(); | 48 | Compose(); |
| 46 | const auto ticks = GetNextTicks(); | 49 | |
| 47 | const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count(); | ||
| 48 | const s64 time_passed = time_end - time_start; | ||
| 49 | const s64 next_time = std::max<s64>(0, ticks - time_passed - delay); | ||
| 50 | guard->unlock(); | 50 | guard->unlock(); |
| 51 | if (next_time > 0) { | ||
| 52 | std::this_thread::sleep_for(std::chrono::nanoseconds{next_time}); | ||
| 53 | } | ||
| 54 | delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time; | ||
| 55 | } | 51 | } |
| 56 | } | 52 | } |
| 57 | 53 | ||
| @@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr | |||
| 66 | guard = std::make_shared<std::mutex>(); | 62 | guard = std::make_shared<std::mutex>(); |
| 67 | 63 | ||
| 68 | // Schedule the screen composition events | 64 | // Schedule the screen composition events |
| 69 | composition_event = Core::Timing::CreateEvent( | 65 | multi_composition_event = Core::Timing::CreateEvent( |
| 66 | "ScreenComposition", | ||
| 67 | [this](std::uintptr_t, s64 time, | ||
| 68 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | ||
| 69 | vsync_signal.store(true); | ||
| 70 | vsync_signal.notify_all(); | ||
| 71 | return std::chrono::nanoseconds(GetNextTicks()); | ||
| 72 | }); | ||
| 73 | |||
| 74 | single_composition_event = Core::Timing::CreateEvent( | ||
| 70 | "ScreenComposition", | 75 | "ScreenComposition", |
| 71 | [this](std::uintptr_t, s64 time, | 76 | [this](std::uintptr_t, s64 time, |
| 72 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | 77 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { |
| 73 | const auto lock_guard = Lock(); | 78 | const auto lock_guard = Lock(); |
| 74 | Compose(); | 79 | Compose(); |
| 75 | 80 | ||
| 76 | return std::max(std::chrono::nanoseconds::zero(), | 81 | return std::chrono::nanoseconds(GetNextTicks()); |
| 77 | std::chrono::nanoseconds(GetNextTicks()) - ns_late); | ||
| 78 | }); | 82 | }); |
| 79 | 83 | ||
| 80 | if (system.IsMulticore()) { | 84 | if (system.IsMulticore()) { |
| 85 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); | ||
| 81 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); | 86 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); |
| 82 | } else { | 87 | } else { |
| 83 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); | 88 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); |
| 84 | } | 89 | } |
| 85 | } | 90 | } |
| 86 | 91 | ||
| 87 | NVFlinger::~NVFlinger() { | 92 | NVFlinger::~NVFlinger() { |
| 88 | if (!system.IsMulticore()) { | 93 | if (system.IsMulticore()) { |
| 89 | system.CoreTiming().UnscheduleEvent(composition_event, 0); | 94 | system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); |
| 95 | vsync_thread.request_stop(); | ||
| 96 | vsync_signal.store(true); | ||
| 97 | vsync_signal.notify_all(); | ||
| 98 | } else { | ||
| 99 | system.CoreTiming().UnscheduleEvent(single_composition_event, {}); | ||
| 90 | } | 100 | } |
| 91 | 101 | ||
| 92 | for (auto& display : displays) { | 102 | for (auto& display : displays) { |
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 4775597cc..044ac6ac8 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h | |||
| @@ -126,12 +126,15 @@ private: | |||
| 126 | u32 swap_interval = 1; | 126 | u32 swap_interval = 1; |
| 127 | 127 | ||
| 128 | /// Event that handles screen composition. | 128 | /// Event that handles screen composition. |
| 129 | std::shared_ptr<Core::Timing::EventType> composition_event; | 129 | std::shared_ptr<Core::Timing::EventType> multi_composition_event; |
| 130 | std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||
| 130 | 131 | ||
| 131 | std::shared_ptr<std::mutex> guard; | 132 | std::shared_ptr<std::mutex> guard; |
| 132 | 133 | ||
| 133 | Core::System& system; | 134 | Core::System& system; |
| 134 | 135 | ||
| 136 | std::atomic<bool> vsync_signal; | ||
| 137 | |||
| 135 | std::jthread vsync_thread; | 138 | std::jthread vsync_thread; |
| 136 | 139 | ||
| 137 | KernelHelpers::ServiceContext service_context; | 140 | KernelHelpers::ServiceContext service_context; |