From 048b8aef5856117214839a75e7decb8b7cb1f91e Mon Sep 17 00:00:00 2001
From: squidbus <175574877+squidbus@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:28:35 -0700
Subject: [PATCH] videoout: Make present thread realtime on macOS. (#990)

---
 src/common/thread.cpp                  | 43 ++++++++++++++++++++++++++
 src/common/thread.h                    |  3 ++
 src/core/libraries/videoout/driver.cpp |  6 ++--
 3 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index f08b36fa..d1b22547 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -9,6 +9,7 @@
 #include "common/thread.h"
 #ifdef __APPLE__
 #include <mach/mach.h>
+#include <mach/mach_time.h>
 #include <pthread.h>
 #elif defined(_WIN32)
 #include <windows.h>
@@ -31,6 +32,48 @@
 
 namespace Common {
 
+#ifdef __APPLE__
+
+void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
+    // CPU time to grant.
+    const std::chrono::nanoseconds computation_ns = period_ns / 2;
+
+    // Determine the timebase for converting time to ticks.
+    struct mach_timebase_info timebase {};
+    mach_timebase_info(&timebase);
+    const auto ticks_per_ns =
+        static_cast<double>(timebase.denom) / static_cast<double>(timebase.numer);
+
+    const auto period_ticks =
+        static_cast<u32>(static_cast<double>(period_ns.count()) * ticks_per_ns);
+    const auto computation_ticks =
+        static_cast<u32>(static_cast<double>(computation_ns.count()) * ticks_per_ns);
+
+    thread_time_constraint_policy policy = {
+        .period = period_ticks,
+        .computation = computation_ticks,
+        // Should not matter since preemptible is false, but needs to be >= computation regardless.
+        .constraint = computation_ticks,
+        .preemptible = false,
+    };
+
+    int ret = thread_policy_set(
+        pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY,
+        reinterpret_cast<thread_policy_t>(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+    if (ret != KERN_SUCCESS) {
+        LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}",
+                  period_ns.count(), ret);
+    }
+}
+
+#else
+
+void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
+    // Not implemented
+}
+
+#endif
+
 #ifdef _WIN32
 
 void SetCurrentThreadPriority(ThreadPriority new_priority) {
diff --git a/src/common/thread.h b/src/common/thread.h
index 39acc1db..3ee60c72 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <chrono>
 #include "common/types.h"
 
 namespace Common {
@@ -16,6 +17,8 @@ enum class ThreadPriority : u32 {
     Critical = 4,
 };
 
+void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns);
+
 void SetCurrentThreadPriority(ThreadPriority new_priority);
 
 void SetCurrentThreadName(const char* name);
diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp
index 8fcdd118..f04fb505 100644
--- a/src/core/libraries/videoout/driver.cpp
+++ b/src/core/libraries/videoout/driver.cpp
@@ -261,8 +261,11 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_
 }
 
 void VideoOutDriver::PresentThread(std::stop_token token) {
-    static constexpr std::chrono::milliseconds VblankPeriod{16};
+    static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
+    const auto vblank_period = VblankPeriod / Config::vblankDiv();
+
     Common::SetCurrentThreadName("PresentThread");
+    Common::SetCurrentThreadRealtime(vblank_period);
 
     const auto receive_request = [this] -> Request {
         std::scoped_lock lk{mutex};
@@ -274,7 +277,6 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
         return {};
     };
 
-    auto vblank_period = VblankPeriod / Config::vblankDiv();
     auto delay = std::chrono::microseconds{0};
     while (!token.stop_requested()) {
         // Sleep for most of the vblank duration.