diff options
Diffstat (limited to 'src/core/core_timing.cpp')
-rw-r--r-- | src/core/core_timing.cpp | 104 |
1 files changed, 85 insertions, 19 deletions
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index c2f0f609f..6c0fcb7b5 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -1,6 +1,5 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <mutex> @@ -21,10 +20,11 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac } struct CoreTiming::Event { - u64 time; + s64 time; u64 fifo_order; std::uintptr_t user_data; std::weak_ptr<EventType> type; + s64 reschedule_time; // Sort by time, unless the times are the same, in which case sort by // the order added to the queue @@ -43,10 +43,10 @@ CoreTiming::CoreTiming() CoreTiming::~CoreTiming() = default; void CoreTiming::ThreadEntry(CoreTiming& instance) { - constexpr char name[] = "yuzu:HostTiming"; + constexpr char name[] = "HostTiming"; MicroProfileOnThreadCreate(name); Common::SetCurrentThreadName(name); - Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); + Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); instance.on_thread_init(); instance.ThreadLoop(); MicroProfileOnThreadExit(); @@ -57,7 +57,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) { event_fifo_id = 0; shutting_down = false; ticks = 0; - const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {}; + const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds) + -> std::optional<std::chrono::nanoseconds> { return std::nullopt; }; ev_lost = CreateEvent("_lost_event", empty_timed_callback); if (is_multicore) { timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this)); @@ -80,12 +81,17 @@ void CoreTiming::Shutdown() { void CoreTiming::Pause(bool is_paused) { paused = is_paused; pause_event.Set(); + + if (!is_paused) { + pause_end_time = GetGlobalTimeNs().count(); + } } void CoreTiming::SyncPause(bool is_paused) { if (is_paused == paused && paused_set == paused) { return; } + Pause(is_paused); if (timer_thread) { if (!is_paused) { @@ -95,6 +101,10 @@ void CoreTiming::SyncPause(bool is_paused) { while (paused_set != is_paused) ; } + + if (!is_paused) { + pause_end_time = GetGlobalTimeNs().count(); + } } bool CoreTiming::IsRunning() const { @@ -107,15 +117,33 @@ bool CoreTiming::HasPendingEvents() const { void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, const std::shared_ptr<EventType>& event_type, - std::uintptr_t user_data) { + std::uintptr_t user_data, bool absolute_time) { { std::scoped_lock scope{basic_lock}; - const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count()); + const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future}; + + event_queue.emplace_back( + Event{next_time.count(), event_fifo_id++, user_data, event_type, 0}); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + } - event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type}); + event.Set(); +} + +void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, + std::chrono::nanoseconds resched_time, + const std::shared_ptr<EventType>& 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}; + + 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<>()); } + event.Set(); } @@ -194,20 +222,39 @@ std::optional<s64> CoreTiming::Advance() { Event evt = std::move(event_queue.front()); std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); event_queue.pop_back(); - basic_lock.unlock(); if (const auto event_type{evt.type.lock()}) { - event_type->callback( - evt.user_data, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)}); + basic_lock.unlock(); + + const auto new_schedule_time{event_type->callback( + evt.user_data, evt.time, + std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})}; + + basic_lock.lock(); + + if (evt.reschedule_time != 0) { + 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<>()); + } } - basic_lock.lock(); global_timer = GetGlobalTimeNs().count(); } 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; } @@ -220,16 +267,35 @@ 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, 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)); + } + + while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { + // Yield to reduce thread starvation. + std::this_thread::yield(); + } + + 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(); } wait_set = false; } + paused_set = true; clock->Pause(true); pause_event.Wait(); |