Merge pull request #8650 from Kelebek1/vsync
[Coretiming/NVNFlinger] Improve multi-core vsync timing, and core timing accuracy
This commit is contained in:
		| @@ -54,6 +54,10 @@ public: | ||||
|         is_set = false; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] bool IsSet() { | ||||
|         return is_set; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::condition_variable condvar; | ||||
|     std::mutex mutex; | ||||
|   | ||||
| @@ -134,6 +134,7 @@ 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}; | ||||
|  | ||||
| @@ -141,6 +142,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, | ||||
|             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(); | ||||
| } | ||||
|  | ||||
| void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, | ||||
| @@ -229,17 +233,17 @@ std::optional<s64> 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<>()); | ||||
| @@ -250,8 +254,7 @@ std::optional<s64> 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; | ||||
|     } | ||||
| @@ -264,11 +267,29 @@ 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(); | ||||
|             } | ||||
|   | ||||
| @@ -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<s64>(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<std::mutex>(); | ||||
|  | ||||
|     // 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<std::chrono::nanoseconds> { | ||||
|             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<std::chrono::nanoseconds> { | ||||
|             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) { | ||||
|   | ||||
| @@ -126,12 +126,15 @@ private: | ||||
|     u32 swap_interval = 1; | ||||
|  | ||||
|     /// Event that handles screen composition. | ||||
|     std::shared_ptr<Core::Timing::EventType> composition_event; | ||||
|     std::shared_ptr<Core::Timing::EventType> multi_composition_event; | ||||
|     std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||||
|  | ||||
|     std::shared_ptr<std::mutex> guard; | ||||
|  | ||||
|     Core::System& system; | ||||
|  | ||||
|     std::atomic<bool> vsync_signal; | ||||
|  | ||||
|     std::jthread vsync_thread; | ||||
|  | ||||
|     KernelHelpers::ServiceContext service_context; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user