perf: sync native danmaku with screen refresh

This commit is contained in:
dela
2026-06-21 21:30:57 +08:00
parent 907de1feeb
commit cf58267d41
2 changed files with 93 additions and 13 deletions
@@ -12,7 +12,9 @@
#include <QRectF> #include <QRectF>
#include <QResizeEvent> #include <QResizeEvent>
#include <QScreen> #include <QScreen>
#include <QShowEvent>
#include <QTimer> #include <QTimer>
#include <QWindow>
#include <QtGlobal> #include <QtGlobal>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@@ -26,7 +28,9 @@ constexpr qreal kReferenceHeight = 1080.0;
constexpr qreal kTopMargin = 24.0; constexpr qreal kTopMargin = 24.0;
constexpr qreal kBottomMargin = 48.0; constexpr qreal kBottomMargin = 48.0;
constexpr int kResizeRebuildDebounceMs = 48; constexpr int kResizeRebuildDebounceMs = 48;
constexpr int kDefaultFrameIntervalMs = 8; constexpr int kFallbackFrameIntervalMs = 16;
constexpr int kMinFrameIntervalMs = 4;
constexpr int kMaxFrameIntervalMs = 33;
constexpr qreal kScrollStartPadding = 32.0; constexpr qreal kScrollStartPadding = 32.0;
constexpr qreal kScrollEndPadding = 48.0; constexpr qreal kScrollEndPadding = 48.0;
constexpr qreal kScrollLaneGapPadding = 96.0; constexpr qreal kScrollLaneGapPadding = 96.0;
@@ -150,7 +154,7 @@ NativeDanmakuOverlay::NativeDanmakuOverlay(MpvController *controller,
m_frameTimer = new QTimer(this); m_frameTimer = new QTimer(this);
m_frameTimer->setTimerType(Qt::PreciseTimer); m_frameTimer->setTimerType(Qt::PreciseTimer);
m_frameTimer->setInterval(kDefaultFrameIntervalMs); m_frameTimer->setInterval(kFallbackFrameIntervalMs);
connect(m_frameTimer, &QTimer::timeout, this, [this]() { connect(m_frameTimer, &QTimer::timeout, this, [this]() {
const qint64 positionMs = currentPositionMs(); const qint64 positionMs = currentPositionMs();
if (m_lastResolvedPositionMs < 0 || positionMs < m_lastResolvedPositionMs || if (m_lastResolvedPositionMs < 0 || positionMs < m_lastResolvedPositionMs ||
@@ -168,6 +172,8 @@ NativeDanmakuOverlay::NativeDanmakuOverlay(MpvController *controller,
connect(m_scheduleRebuildTimer, &QTimer::timeout, this, connect(m_scheduleRebuildTimer, &QTimer::timeout, this,
&NativeDanmakuOverlay::rebuildScheduleNow); &NativeDanmakuOverlay::rebuildScheduleNow);
updateScreenRefreshBinding();
if (!m_controller) { if (!m_controller) {
return; return;
} }
@@ -331,6 +337,13 @@ void NativeDanmakuOverlay::resizeEvent(QResizeEvent *event)
{ {
QWidget::resizeEvent(event); QWidget::resizeEvent(event);
requestScheduleRebuild(false, false); requestScheduleRebuild(false, false);
updateScreenRefreshBinding();
}
void NativeDanmakuOverlay::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
updateScreenRefreshBinding();
} }
void NativeDanmakuOverlay::rebuildSchedule() void NativeDanmakuOverlay::rebuildSchedule()
@@ -653,20 +666,76 @@ void NativeDanmakuOverlay::syncFrameTimer()
int NativeDanmakuOverlay::targetFrameIntervalMs() const int NativeDanmakuOverlay::targetFrameIntervalMs() const
{ {
const QScreen *targetScreen = screen(); const QScreen *screen = targetScreen();
if (!targetScreen && window()) { const qreal refreshRate = screen ? screen->refreshRate() : 0.0;
targetScreen = window()->screen();
}
const qreal refreshRate = targetScreen ? targetScreen->refreshRate() : 0.0;
if (refreshRate <= 0.0) { if (refreshRate <= 0.0) {
return kDefaultFrameIntervalMs; return kFallbackFrameIntervalMs;
} }
const qreal desiredRefreshRate = std::max<qreal>(120.0, refreshRate); return qBound(kMinFrameIntervalMs,
return qBound(4, static_cast<int>(std::lround(1000.0 / refreshRate)),
static_cast<int>(std::lround(1000.0 / desiredRefreshRate)), kMaxFrameIntervalMs);
kDefaultFrameIntervalMs); }
QScreen *NativeDanmakuOverlay::targetScreen() const
{
if (QScreen *widgetScreen = screen()) {
return widgetScreen;
}
if (const QWidget *topLevel = window()) {
if (QWindow *handle = topLevel->windowHandle()) {
if (QScreen *windowScreen = handle->screen()) {
return windowScreen;
}
}
if (QScreen *windowScreen = topLevel->screen()) {
return windowScreen;
}
}
return nullptr;
}
void NativeDanmakuOverlay::updateScreenRefreshBinding()
{
QWindow *currentWindow = nullptr;
if (QWidget *topLevel = window()) {
currentWindow = topLevel->windowHandle();
}
if (m_observedWindow != currentWindow) {
if (m_windowScreenConnection) {
QObject::disconnect(m_windowScreenConnection);
m_windowScreenConnection = {};
}
m_observedWindow = currentWindow;
if (m_observedWindow) {
m_windowScreenConnection =
connect(m_observedWindow, &QWindow::screenChanged, this,
[this](QScreen *) {
updateScreenRefreshBinding();
syncFrameTimer();
});
}
}
QScreen *currentScreen = targetScreen();
if (m_observedScreen == currentScreen) {
syncFrameTimer();
return;
}
if (m_screenRefreshConnection) {
QObject::disconnect(m_screenRefreshConnection);
m_screenRefreshConnection = {};
}
m_observedScreen = currentScreen;
if (m_observedScreen) {
m_screenRefreshConnection =
connect(m_observedScreen, &QScreen::refreshRateChanged, this,
[this](qreal) { syncFrameTimer(); });
}
syncFrameTimer();
} }
void NativeDanmakuOverlay::refreshBasePosition(qint64 positionMs) void NativeDanmakuOverlay::refreshBasePosition(qint64 positionMs)
@@ -3,6 +3,7 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QHash> #include <QHash>
#include <QMetaObject>
#include <QPixmap> #include <QPixmap>
#include <QPointer> #include <QPointer>
#include <QSize> #include <QSize>
@@ -14,9 +15,12 @@
class MpvController; class MpvController;
class QPaintEvent; class QPaintEvent;
class QResizeEvent; class QResizeEvent;
class QScreen;
class QShowEvent;
class QTimer; class QTimer;
class QVariant; class QVariant;
class QRectF; class QRectF;
class QWindow;
class NativeDanmakuOverlay : public QWidget class NativeDanmakuOverlay : public QWidget
{ {
@@ -35,6 +39,7 @@ public:
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
private: private:
enum class ItemType { enum class ItemType {
@@ -66,6 +71,8 @@ private:
void clearScheduledState(bool clearSpriteCache = false); void clearScheduledState(bool clearSpriteCache = false);
void syncFrameTimer(); void syncFrameTimer();
int targetFrameIntervalMs() const; int targetFrameIntervalMs() const;
QScreen *targetScreen() const;
void updateScreenRefreshBinding();
void refreshBasePosition(qint64 positionMs); void refreshBasePosition(qint64 positionMs);
qreal currentPositionMsPrecise() const; qreal currentPositionMsPrecise() const;
qint64 currentPositionMs() const; qint64 currentPositionMs() const;
@@ -80,6 +87,10 @@ private:
QPointer<MpvController> m_controller; QPointer<MpvController> m_controller;
QTimer *m_frameTimer = nullptr; QTimer *m_frameTimer = nullptr;
QTimer *m_scheduleRebuildTimer = nullptr; QTimer *m_scheduleRebuildTimer = nullptr;
QPointer<QWindow> m_observedWindow;
QPointer<QScreen> m_observedScreen;
QMetaObject::Connection m_windowScreenConnection;
QMetaObject::Connection m_screenRefreshConnection;
QElapsedTimer m_positionClock; QElapsedTimer m_positionClock;
QList<DanmakuComment> m_comments; QList<DanmakuComment> m_comments;
QList<DanmakuComment> m_sortedComments; QList<DanmakuComment> m_sortedComments;