mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 18:03:04 +02:00
Co-authored-by: love-gamer <340996855+love-gamer@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
3.4 KiB
C++
112 lines
3.4 KiB
C++
// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See vblank_timer.h.
|
|
#include "vblank_timer.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <string_view>
|
|
|
|
#ifdef __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
// macOS only: iOS keeps the ordinary thread (its vblank timing hasn't been measured).
|
|
#if defined(__APPLE__) && !TARGET_OS_IOS
|
|
#define SB_REALTIME_VBLANK 1
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_time.h>
|
|
#include <mach/thread_policy.h>
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
namespace video
|
|
{
|
|
namespace
|
|
{
|
|
#ifdef SB_REALTIME_VBLANK
|
|
uint32_t ToMach(int64_t ns)
|
|
{
|
|
static const mach_timebase_info_data_t timebase = [] {
|
|
mach_timebase_info_data_t tb{};
|
|
mach_timebase_info(&tb);
|
|
return tb;
|
|
}();
|
|
return uint32_t(double(std::max<int64_t>(ns, 0)) * double(timebase.denom) / double(timebase.numer));
|
|
}
|
|
#endif
|
|
|
|
int64_t Percentile(std::vector<int64_t> v, size_t permille)
|
|
{
|
|
if (v.empty())
|
|
return 0;
|
|
size_t k = std::min(v.size() - 1, v.size() * permille / 1000);
|
|
std::nth_element(v.begin(), v.begin() + ptrdiff_t(k), v.end());
|
|
return v[k];
|
|
}
|
|
}
|
|
|
|
VblankTimer ChosenVblankTimer()
|
|
{
|
|
const char* v = std::getenv("NFSMW_VBLANK_TIMER");
|
|
return v && std::string_view(v) == "sleep" ? VblankTimer::Ordinary : VblankTimer::RealTime;
|
|
}
|
|
|
|
bool PrepareVblankThread(VblankTimer timer, int64_t periodNs)
|
|
{
|
|
#ifdef SB_REALTIME_VBLANK
|
|
if (timer != VblankTimer::RealTime)
|
|
return false;
|
|
// Woken at its time, ahead of the game's own threads: it runs the
|
|
// guest's vblank handler (microseconds) and sleeps again.
|
|
thread_time_constraint_policy_data_t policy{};
|
|
policy.period = ToMach(periodNs);
|
|
policy.computation = ToMach(1'000'000);
|
|
policy.constraint = ToMach(2'000'000);
|
|
policy.preemptible = 1;
|
|
return thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY,
|
|
reinterpret_cast<thread_policy_t>(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
|
|
#else
|
|
(void)timer;
|
|
(void)periodNs;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
const char* VblankTimerName(VblankTimer timer, bool realtime)
|
|
{
|
|
#ifdef SB_REALTIME_VBLANK
|
|
if (timer == VblankTimer::RealTime)
|
|
return realtime ? "real-time" : "ordinary (real-time refused)";
|
|
return "ordinary (NFSMW_VBLANK_TIMER=sleep)";
|
|
#else
|
|
(void)timer;
|
|
(void)realtime;
|
|
return "ordinary";
|
|
#endif
|
|
}
|
|
|
|
void VblankTiming::Add(int64_t lateNs, int64_t handlerNs)
|
|
{
|
|
if (m_late.size() < kCount)
|
|
{
|
|
m_late.push_back(lateNs);
|
|
m_handler.push_back(handlerNs);
|
|
return;
|
|
}
|
|
m_late[m_next] = lateNs;
|
|
m_handler[m_next] = handlerNs;
|
|
m_next = (m_next + 1) % kCount;
|
|
}
|
|
|
|
VblankTiming::Summary VblankTiming::Summarize() const
|
|
{
|
|
Summary s;
|
|
s.count = uint32_t(m_late.size());
|
|
if (m_late.empty())
|
|
return s;
|
|
s.lateMedianNs = double(Percentile(m_late, 500));
|
|
s.late99Ns = double(Percentile(m_late, 990));
|
|
s.lateMaxNs = double(*std::max_element(m_late.begin(), m_late.end()));
|
|
s.handler99Ns = double(Percentile(m_handler, 990));
|
|
return s;
|
|
}
|
|
}
|