1
0
Fork 0
firefox/widget/gtk/WaylandVsyncSource.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

363 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef MOZ_WAYLAND
# include "WaylandVsyncSource.h"
# include "mozilla/UniquePtr.h"
# include "nsThreadUtils.h"
# include "nsISupportsImpl.h"
# include "MainThreadUtils.h"
# include "mozilla/ScopeExit.h"
# include "nsGtkUtils.h"
# include "mozilla/StaticPrefs_layout.h"
# include "mozilla/StaticPrefs_widget.h"
# include "nsWindow.h"
# include <gdk/gdkwayland.h>
# ifdef MOZ_LOGGING
# include "mozilla/Logging.h"
# include "nsTArray.h"
# include "Units.h"
extern mozilla::LazyLogModule gWidgetVsync;
# undef LOG
# define LOG(str, ...) \
MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, \
("[%p]: " str, GetWindowForLogging(), ##__VA_ARGS__))
# define LOGS(str, ...) \
MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__))
# else
# define LOG(...)
# endif /* MOZ_LOGGING */
using namespace mozilla::widget;
namespace mozilla {
static float GetFPS(TimeDuration aVsyncRate) {
return 1000.0f / float(aVsyncRate.ToMilliseconds());
}
MOZ_RUNINIT static nsTArray<WaylandVsyncSource*> gWaylandVsyncSources;
Maybe<TimeDuration> WaylandVsyncSource::GetFastestVsyncRate() {
Maybe<TimeDuration> retVal;
for (auto* source : gWaylandVsyncSources) {
auto rate = source->GetVsyncRateIfEnabled();
if (!rate) {
continue;
}
if (!retVal.isSome()) {
retVal.emplace(*rate);
} else if (*rate < *retVal) {
retVal.ref() = *rate;
}
}
return retVal;
}
// Lint forces us to take this reference outside of constructor
void WaylandVsyncSource::Init() {
MutexAutoLock lock(mMutex);
WaylandSurfaceLock surfaceLock(mWaylandSurface);
// mWaylandSurface is shared and referenced by nsWindow,
// MozContained and WaylandVsyncSource.
//
// All references are explicitly removed at nsWindow::Destroy()
// by WaylandVsyncSource::Shutdown() and
// releases mWaylandSurface / MozContainer release.
//
// WaylandVsyncSource can be used by layour code after
// nsWindow::Destroy()/WaylandVsyncSource::Shutdown() but
// only as an empty shell.
mWaylandSurface->AddPersistentFrameCallbackLocked(
surfaceLock,
[this, self = RefPtr{this}](wl_callback* aCallback,
uint32_t aTime) -> void {
{
MutexAutoLock lock(mMutex);
if (!mVsyncSourceEnabled || !mVsyncEnabled || !mWaylandSurface) {
return;
}
if (aTime && mLastFrameTime == aTime) {
return;
}
mLastFrameTime = aTime;
}
LOG("WaylandVsyncSource frame callback, routed %d time %d", !aCallback,
aTime);
VisibleWindowCallback(aTime);
// If attached WaylandSurface becomes hidden/obscured or unmapped,
// we will not get any regular frame callback any more and we will
// not get any notification of it.
// So always set up hidden window callback to catch it up.
SetHiddenWindowVSync();
},
/* aEmulateFrameCallback */ true);
}
WaylandVsyncSource::WaylandVsyncSource(nsWindow* aWindow)
: mMutex("WaylandVsyncSource"),
mWindow(aWindow),
mWaylandSurface(MOZ_WL_SURFACE(aWindow->GetMozContainer())),
mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
mLastVsyncTimeStamp(TimeStamp::Now()),
mHiddenWindowTimeout(1000 / StaticPrefs::layout_throttled_frame_rate()) {
MOZ_ASSERT(NS_IsMainThread());
gWaylandVsyncSources.AppendElement(this);
LOG("WaylandVsyncSource::WaylandVsyncSource()");
}
WaylandVsyncSource::~WaylandVsyncSource() {
LOG("WaylandVsyncSource::~WaylandVsyncSource()");
gWaylandVsyncSources.RemoveElement(this);
}
void WaylandVsyncSource::SetHiddenWindowVSync() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
if (!mHiddenWindowTimerID) {
LOG("WaylandVsyncSource::SetHiddenWindowVSync()");
mHiddenWindowTimerID = g_timeout_add(
mHiddenWindowTimeout,
[](void* data) -> gint {
RefPtr vsync = static_cast<WaylandVsyncSource*>(data);
if (vsync->HiddenWindowCallback()) {
// We want to fire again, so don't clear mHiddenWindowTimerID
return G_SOURCE_CONTINUE;
}
// No need for g_source_remove, caller does it for us.
vsync->mHiddenWindowTimerID = 0;
return G_SOURCE_REMOVE;
},
this);
}
}
void WaylandVsyncSource::SetVSyncEventsLocked(const MutexAutoLock& aProofOfLock,
bool aEnabled) {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
mMutex.AssertCurrentThreadOwns();
if (aEnabled) {
mLastVsyncTimeStamp = TimeStamp::Now();
} else {
MozClearHandleID(mHiddenWindowTimerID, g_source_remove);
}
mWaylandSurface->SetFrameCallbackState(aEnabled);
}
void WaylandVsyncSource::EnableVsync() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
LOG("WaylandVsyncSource::EnableVsync fps %f\n", GetFPS(mVsyncRate));
if (mVsyncEnabled || mIsShutdown) {
LOG(" early quit");
return;
}
mVsyncEnabled = true;
SetVSyncEventsLocked(lock, mVsyncEnabled && mVsyncSourceEnabled);
}
void WaylandVsyncSource::DisableVsync() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
LOG("WaylandVsyncSource::DisableVsync fps %f\n", GetFPS(mVsyncRate));
if (!mVsyncEnabled || mIsShutdown) {
LOG(" early quit");
return;
}
mVsyncEnabled = false;
SetVSyncEventsLocked(lock, mVsyncEnabled && mVsyncSourceEnabled);
}
void WaylandVsyncSource::EnableVSyncSource() {
MutexAutoLock lock(mMutex);
LOG("WaylandVsyncSource::EnableVSyncSource() WaylandSurface [%p] fps %f",
mWaylandSurface.get(), GetFPS(mVsyncRate));
mVsyncSourceEnabled = true;
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(mWaylandSurface);
SetVSyncEventsLocked(lock, mVsyncEnabled && mVsyncSourceEnabled);
}
void WaylandVsyncSource::DisableVSyncSource() {
MutexAutoLock lock(mMutex);
LOG("WaylandVsyncSource::DisableVSyncSource() WaylandSurface [%p]",
mWaylandSurface.get());
mVsyncSourceEnabled = false;
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(mWaylandSurface);
SetVSyncEventsLocked(lock, mVsyncEnabled && mVsyncSourceEnabled);
}
bool WaylandVsyncSource::HiddenWindowCallback() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
RefPtr<nsWindow> window;
TimeStamp lastVSync;
TimeStamp outputTimestamp;
{
MutexAutoLock lock(mMutex);
if (!mVsyncSourceEnabled || !mVsyncEnabled) {
// We are unwanted by either our creator or our consumer, so we just stop
// here without setting up a new frame callback.
LOG("WaylandVsyncSource::HiddenWindowCallback(): quit, mVsyncEnabled %d "
"mWaylandSurface %p",
mVsyncEnabled && mVsyncSourceEnabled, mWaylandSurface.get());
return false;
}
const auto now = TimeStamp::Now();
const auto timeSinceLastVSync = now - mLastVsyncTimeStamp;
if (timeSinceLastVSync.ToMilliseconds() < mHiddenWindowTimeout) {
// We're not hidden, keep firing the callback to monitor window state.
// If we become hidden we want set window occlusion state from this
// callback.
return true;
}
LOG("WaylandVsyncSource::HiddenWindowCallback() we're hidden, time since "
"last VSync %d ms",
(int)timeSinceLastVSync.ToMilliseconds());
CalculateVsyncRateLocked(lock, now);
mLastVsyncTimeStamp = lastVSync = now;
outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
window = mWindow;
}
// This could disable vsync.
window->NotifyOcclusionState(OcclusionState::OCCLUDED);
if (window->IsDestroyed()) {
return false;
}
// Make sure to fire vsync now even if we get disabled afterwards.
// This gives an opportunity to clean up after the visibility state change.
// FIXME: Do we really need to do this?
NotifyVsync(lastVSync, outputTimestamp);
return StaticPrefs::widget_wayland_vsync_keep_firing_at_idle();
}
void WaylandVsyncSource::VisibleWindowCallback(uint32_t aTime) {
# ifdef MOZ_LOGGING
if (!aTime) {
LOG("WaylandVsyncSource::EmulatedWindowCallback()");
} else {
LOG("WaylandVsyncSource::VisibleWindowCallback() time %d", aTime);
}
# endif
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
{
// This might enable vsync.
RefPtr window = mWindow;
window->NotifyOcclusionState(OcclusionState::VISIBLE);
// NotifyOcclusionState can destroy us.
if (window->IsDestroyed()) {
return;
}
}
MutexAutoLock lock(mMutex);
if (!mVsyncEnabled || !mVsyncSourceEnabled) {
// We are unwanted by either our creator or our consumer, so we just stop
// here without setting up a new frame callback.
LOG(" quit, mVsyncEnabled %d mWaylandSurface %p",
mVsyncEnabled && mVsyncSourceEnabled, mWaylandSurface.get());
return;
}
const auto now = TimeStamp::Now();
TimeStamp vsyncTimestamp;
if (aTime) {
const auto callbackTimeStamp = TimeStamp::FromSystemTime(
BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime));
// If the callback timestamp is close enough to our timestamp, use it,
// otherwise use the current time.
vsyncTimestamp = std::abs((now - callbackTimeStamp).ToMilliseconds()) < 50.0
? callbackTimeStamp
: now;
} else {
vsyncTimestamp = now;
}
CalculateVsyncRateLocked(lock, vsyncTimestamp);
mLastVsyncTimeStamp = vsyncTimestamp;
const TimeStamp outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
{
MutexAutoUnlock unlock(mMutex);
NotifyVsync(vsyncTimestamp, outputTimestamp);
}
}
TimeDuration WaylandVsyncSource::GetVsyncRate() {
MutexAutoLock lock(mMutex);
return mVsyncRate;
}
Maybe<TimeDuration> WaylandVsyncSource::GetVsyncRateIfEnabled() {
MutexAutoLock lock(mMutex);
if (!mVsyncEnabled) {
return Nothing();
}
return Some(mVsyncRate);
}
void WaylandVsyncSource::CalculateVsyncRateLocked(
const MutexAutoLock& aProofOfLock, TimeStamp aVsyncTimestamp) {
mMutex.AssertCurrentThreadOwns();
double duration = (aVsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
double curVsyncRate = mVsyncRate.ToMilliseconds();
LOG("WaylandVsyncSource::CalculateVsyncRateLocked start fps %f\n",
GetFPS(mVsyncRate));
double correction;
if (duration > curVsyncRate) {
correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
mVsyncRate += TimeDuration::FromMilliseconds(correction);
} else {
correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10);
mVsyncRate -= TimeDuration::FromMilliseconds(correction);
}
LOG(" new fps %f correction %f\n", GetFPS(mVsyncRate), correction);
}
bool WaylandVsyncSource::IsVsyncEnabled() {
MutexAutoLock lock(mMutex);
return mVsyncEnabled && mWaylandSurface;
}
void WaylandVsyncSource::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
LOG("WaylandVsyncSource::Shutdown fps %f\n", GetFPS(mVsyncRate));
mWaylandSurface = nullptr;
mWindow = nullptr;
mIsShutdown = true;
mVsyncEnabled = false;
mVsyncSourceEnabled = false;
MozClearHandleID(mHiddenWindowTimerID, g_source_remove);
}
} // namespace mozilla
#endif // MOZ_WAYLAND