mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 18:03:04 +02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
4.5 KiB
C++
135 lines
4.5 KiB
C++
// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See vblank_lock.h.
|
|
#include "vblank_lock.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace video
|
|
{
|
|
int64_t VblankLock::Next(int64_t prev)
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
bool locked = m_enabled && !m_follows && m_display.valid;
|
|
if (locked != m_locked)
|
|
{
|
|
m_locked = locked;
|
|
m_steadyVblanks = 0;
|
|
m_slipLeftNs = 0;
|
|
}
|
|
if (!locked)
|
|
return prev + m_fallbackNs;
|
|
const double period = m_display.periodNs;
|
|
m_leadNs = period - double(kAfterRefreshNs);
|
|
double candidate = double(prev) + period;
|
|
if (m_slipLeftNs > 0)
|
|
{
|
|
double step = std::min(double(kMaxStepNs), m_slipLeftNs);
|
|
m_slipLeftNs -= step;
|
|
return int64_t(std::llround(candidate + step));
|
|
}
|
|
// The wanted vblanks are grid + k * period - lead; the nearest one.
|
|
double x = (candidate + m_leadNs - double(m_display.gridNs)) / period;
|
|
m_errorNs = (std::round(x) - x) * period;
|
|
double step = std::clamp(m_errorNs, -double(kMaxStepNs), double(kMaxStepNs));
|
|
m_steadyVblanks = std::abs(m_errorNs) <= double(kMaxStepNs) ? m_steadyVblanks + 1 : 0;
|
|
m_chasingVblanks = std::abs(m_errorNs) > double(kMaxStepNs) ? m_chasingVblanks + 1 : 0;
|
|
if (double(m_chasingVblanks) * double(kMaxStepNs) > period)
|
|
{
|
|
m_follows = true;
|
|
m_locked = false;
|
|
return prev + m_fallbackNs;
|
|
}
|
|
return int64_t(std::llround(candidate + step));
|
|
}
|
|
|
|
void VblankLock::Resumed()
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
m_steadyVblanks = 0;
|
|
m_chasingVblanks = 0;
|
|
m_count = 0;
|
|
}
|
|
|
|
void VblankLock::SetDisplay(const RefreshEstimate& estimate)
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
m_display = estimate;
|
|
}
|
|
|
|
void VblankLock::Observe(int64_t vblank, int64_t presented, int64_t onScreen)
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
uint32_t slot = m_count++ % kHistory;
|
|
m_ready[slot] = float(presented - vblank);
|
|
m_latency[slot] = float(onScreen - vblank);
|
|
m_observed++;
|
|
if (!m_locked)
|
|
{
|
|
m_late[slot] = 0;
|
|
return;
|
|
}
|
|
const double period = m_display.periodNs;
|
|
m_late[slot] = int32_t(std::llround((double(onScreen - vblank) - m_leadNs) / period));
|
|
// Every frame of the last 2 s showed at least a refresh after its
|
|
// target, with the phase settled and no slip for as long: a frame is
|
|
// queued. Slip a refresh to drain it.
|
|
const uint64_t slipFrames = uint64_t(period / double(kMaxStepNs)) + kHistory;
|
|
if (m_slipLeftNs > 0 || m_steadyVblanks < kHistory || m_count < kHistory ||
|
|
(m_slips && m_observed - m_lastSlipObserved < slipFrames))
|
|
return;
|
|
int32_t late = *std::min_element(m_late, m_late + kHistory);
|
|
if (!m_slipJudged)
|
|
{
|
|
m_slipJudged = true;
|
|
if (late >= m_lateAtSlip)
|
|
m_acceptedLate = m_lateAtSlip;
|
|
}
|
|
if (late > m_acceptedLate)
|
|
{
|
|
m_slipLeftNs = period;
|
|
m_slips++;
|
|
m_lastSlipObserved = m_observed;
|
|
m_lateAtSlip = late;
|
|
m_slipJudged = false;
|
|
}
|
|
}
|
|
|
|
bool VblankLock::Locked() const
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
return m_locked;
|
|
}
|
|
|
|
double VblankLock::PeriodNs() const
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
return m_locked ? m_display.periodNs : double(m_fallbackNs);
|
|
}
|
|
|
|
VblankLock::Status VblankLock::GetStatus() const
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
Status s;
|
|
s.locked = m_locked;
|
|
s.periodNs = m_locked ? m_display.periodNs : double(m_fallbackNs);
|
|
s.leadNs = m_leadNs;
|
|
s.errorNs = m_errorNs;
|
|
s.slips = m_slips;
|
|
s.follows = m_follows;
|
|
s.observed = m_observed;
|
|
s.display = m_display;
|
|
uint32_t n = std::min(m_count, kHistory);
|
|
if (n)
|
|
{
|
|
float latency[kHistory], ready[kHistory];
|
|
std::copy(m_latency, m_latency + n, latency);
|
|
std::copy(m_ready, m_ready + n, ready);
|
|
std::nth_element(latency, latency + n / 2, latency + n);
|
|
std::nth_element(ready, ready + n * 99 / 100, ready + n);
|
|
s.latencyNs = latency[n / 2];
|
|
s.readyNs = ready[n * 99 / 100];
|
|
}
|
|
return s;
|
|
}
|
|
}
|