Backed out changeset 795745f450df (bug 1934497) Backed out changeset 6aff7ffeaff4 (bug 1934497) Backed out changeset e6917a8311f2 (bug 1934497) Backed out changeset 2bf4f54a3077 (bug 1934497) Backed out changeset 87613cc32aa4 (bug 1934497) Backed out changeset eea0eef40abf (bug 1934497) Backed out changeset a9a38191e649 (bug 1934497) Backed out changeset 5343854a37e5 (bug 1934497) Backed out changeset 6806bbf97c86 (bug 1934497) Backed out changeset ff8a92afd360 (bug 1934497) Backed out changeset dcef70581e34 (bug 1934497) Backed out changeset c0512daa9188 (bug 1934497) Backed out changeset 693a7c400778 (bug 1934497) Backed out changeset 75d095afa9cf (bug 1934497) Backed out changeset 191397418056 (bug 1934497) Backed out changeset 5ba05279a465 (bug 1934497)
434 lines
13 KiB
C++
434 lines
13 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, \
|
|
("[nsWindow %p]: " str, GetWindowForLogging(), ##__VA_ARGS__))
|
|
# else
|
|
# define LOG(...)
|
|
# endif /* MOZ_LOGGING */
|
|
|
|
using namespace mozilla::widget;
|
|
|
|
namespace mozilla {
|
|
|
|
static void WaylandVsyncSourceCallbackHandler(void* aData,
|
|
struct wl_callback* aCallback,
|
|
uint32_t aTime) {
|
|
RefPtr context = static_cast<WaylandVsyncSource*>(aData);
|
|
context->FrameCallback(aCallback, aTime);
|
|
}
|
|
|
|
static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
|
|
WaylandVsyncSourceCallbackHandler};
|
|
|
|
static void NativeLayerRootWaylandVsyncCallback(void* aData, uint32_t aTime) {
|
|
RefPtr context = static_cast<WaylandVsyncSource*>(aData);
|
|
context->FrameCallback(nullptr, aTime);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
WaylandVsyncSource::WaylandVsyncSource(nsWindow* aWindow)
|
|
: mMutex("WaylandVsyncSource"),
|
|
mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
|
|
mLastVsyncTimeStamp(TimeStamp::Now()),
|
|
mWindow(aWindow),
|
|
mIdleTimeout(1000 / StaticPrefs::layout_throttled_frame_rate()) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
gWaylandVsyncSources.AppendElement(this);
|
|
}
|
|
|
|
WaylandVsyncSource::~WaylandVsyncSource() {
|
|
gWaylandVsyncSources.RemoveElement(this);
|
|
}
|
|
|
|
void WaylandVsyncSource::MaybeUpdateSource(MozContainer* aContainer) {
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
LOG("WaylandVsyncSource::MaybeUpdateSource fps %f", GetFPS(mVsyncRate));
|
|
|
|
if (aContainer == mContainer) {
|
|
LOG(" mContainer is the same, quit.");
|
|
return;
|
|
}
|
|
|
|
mNativeLayerRoot = nullptr;
|
|
mContainer = aContainer;
|
|
|
|
if (mMonitorEnabled) {
|
|
LOG(" monitor enabled, ask for Refresh()");
|
|
mCallbackRequested = false;
|
|
Refresh(lock);
|
|
}
|
|
}
|
|
|
|
void WaylandVsyncSource::MaybeUpdateSource(
|
|
const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot) {
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
LOG("WaylandVsyncSource::MaybeUpdateSource aNativeLayerRoot fps %f",
|
|
GetFPS(mVsyncRate));
|
|
|
|
if (aNativeLayerRoot == mNativeLayerRoot) {
|
|
LOG(" mNativeLayerRoot is the same, quit.");
|
|
return;
|
|
}
|
|
|
|
mNativeLayerRoot = aNativeLayerRoot;
|
|
mContainer = nullptr;
|
|
|
|
if (mMonitorEnabled) {
|
|
LOG(" monitor enabled, ask for Refresh()");
|
|
mCallbackRequested = false;
|
|
Refresh(lock);
|
|
}
|
|
}
|
|
|
|
void WaylandVsyncSource::Refresh(const MutexAutoLock& aProofOfLock)
|
|
MOZ_REQUIRES(mMutex) {
|
|
mMutex.AssertCurrentThreadOwns();
|
|
|
|
LOG("WaylandVsyncSource::Refresh fps %f\n", GetFPS(mVsyncRate));
|
|
|
|
if (!(mContainer || mNativeLayerRoot) || !mMonitorEnabled || !mVsyncEnabled ||
|
|
mCallbackRequested) {
|
|
// We don't need to do anything because:
|
|
// * We are unwanted by our widget or monitor, or
|
|
// * The last frame callback hasn't yet run to see that it had been shut
|
|
// down, so we can reuse it after having set mVsyncEnabled to true.
|
|
LOG(" quit mContainer %d mNativeLayerRoot %d mMonitorEnabled %d "
|
|
"mVsyncEnabled %d mCallbackRequested %d",
|
|
!!mContainer, !!mNativeLayerRoot, mMonitorEnabled, mVsyncEnabled,
|
|
!!mCallbackRequested);
|
|
return;
|
|
}
|
|
|
|
if (mContainer) {
|
|
MozContainerSurfaceLock lock(mContainer);
|
|
struct wl_surface* surface = lock.GetSurface();
|
|
LOG(" refresh from mContainer, wl_surface %p", surface);
|
|
if (!surface) {
|
|
LOG(" we're missing wl_surface, register Refresh() callback");
|
|
// The surface hasn't been created yet. Try again when the surface is
|
|
// ready.
|
|
RefPtr<WaylandVsyncSource> self(this);
|
|
moz_container_wayland_add_initial_draw_callback_locked(
|
|
mContainer, [self]() -> void {
|
|
MutexAutoLock lock(self->mMutex);
|
|
self->Refresh(lock);
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Vsync is enabled, but we don't have a callback configured. Set one up so
|
|
// we can get to work.
|
|
SetupFrameCallback(aProofOfLock);
|
|
const TimeStamp lastVSync = TimeStamp::Now();
|
|
mLastVsyncTimeStamp = lastVSync;
|
|
TimeStamp outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
|
|
|
|
{
|
|
MutexAutoUnlock unlock(mMutex);
|
|
NotifyVsync(lastVSync, outputTimestamp);
|
|
}
|
|
}
|
|
|
|
void WaylandVsyncSource::EnableMonitor() {
|
|
LOG("WaylandVsyncSource::EnableMonitor");
|
|
MutexAutoLock lock(mMutex);
|
|
if (mMonitorEnabled) {
|
|
return;
|
|
}
|
|
mMonitorEnabled = true;
|
|
Refresh(lock);
|
|
}
|
|
|
|
void WaylandVsyncSource::DisableMonitor() {
|
|
LOG("WaylandVsyncSource::DisableMonitor");
|
|
MutexAutoLock lock(mMutex);
|
|
if (!mMonitorEnabled) {
|
|
return;
|
|
}
|
|
mMonitorEnabled = false;
|
|
mCallbackRequested = false;
|
|
}
|
|
|
|
void WaylandVsyncSource::SetupFrameCallback(const MutexAutoLock& aProofOfLock) {
|
|
mMutex.AssertCurrentThreadOwns();
|
|
MOZ_ASSERT(!mCallbackRequested);
|
|
|
|
LOG("WaylandVsyncSource::SetupFrameCallback");
|
|
|
|
if (mNativeLayerRoot) {
|
|
LOG(" use mNativeLayerRoot");
|
|
mNativeLayerRoot->RequestFrameCallback(&NativeLayerRootWaylandVsyncCallback,
|
|
this);
|
|
} else {
|
|
MozContainerSurfaceLock lock(mContainer);
|
|
struct wl_surface* surface = lock.GetSurface();
|
|
LOG(" use mContainer, wl_surface %p", surface);
|
|
if (!surface) {
|
|
// We don't have a surface, either due to being called before it was made
|
|
// available in the mozcontainer, or after it was destroyed. We're all
|
|
// done regardless.
|
|
LOG(" missing wl_surface, quit.");
|
|
return;
|
|
}
|
|
|
|
MozClearPointer(mCallback, wl_callback_destroy);
|
|
mCallback = wl_surface_frame(surface);
|
|
wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
|
|
this);
|
|
|
|
LOG(" register frame callback");
|
|
wl_surface_commit(surface);
|
|
wl_display_flush(WaylandDisplayGet()->GetDisplay());
|
|
|
|
if (!mIdleTimerID) {
|
|
mIdleTimerID = g_timeout_add(
|
|
mIdleTimeout,
|
|
[](void* data) -> gint {
|
|
RefPtr vsync = static_cast<WaylandVsyncSource*>(data);
|
|
if (vsync->IdleCallback()) {
|
|
// We want to fire again, so don't clear mIdleTimerID
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
// No need for g_source_remove, caller does it for us.
|
|
vsync->mIdleTimerID = 0;
|
|
return G_SOURCE_REMOVE;
|
|
},
|
|
this);
|
|
}
|
|
}
|
|
|
|
mCallbackRequested = true;
|
|
}
|
|
|
|
bool WaylandVsyncSource::IdleCallback() {
|
|
LOG("WaylandVsyncSource::IdleCallback");
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<nsWindow> window;
|
|
TimeStamp lastVSync;
|
|
TimeStamp outputTimestamp;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (!mVsyncEnabled || !mMonitorEnabled) {
|
|
// 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 mMonitorEnabled %d", mVsyncEnabled,
|
|
mMonitorEnabled);
|
|
return false;
|
|
}
|
|
|
|
const auto now = TimeStamp::Now();
|
|
const auto timeSinceLastVSync = now - mLastVsyncTimeStamp;
|
|
if (timeSinceLastVSync.ToMilliseconds() < mIdleTimeout) {
|
|
// We're not idle, we want to fire the timer again.
|
|
return true;
|
|
}
|
|
|
|
LOG(" fire idle vsync");
|
|
CalculateVsyncRate(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::FrameCallback(wl_callback* aCallback, uint32_t aTime) {
|
|
LOG("WaylandVsyncSource::FrameCallback");
|
|
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);
|
|
mCallbackRequested = false;
|
|
|
|
// NotifyOcclusionState() can clear and create new mCallback by
|
|
// EnableVsync()/Refresh(). So don't delete newly created frame callback.
|
|
if (aCallback && aCallback == mCallback) {
|
|
MozClearPointer(mCallback, wl_callback_destroy);
|
|
}
|
|
|
|
if (!mVsyncEnabled || !mMonitorEnabled) {
|
|
// 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 mMonitorEnabled %d", mVsyncEnabled,
|
|
mMonitorEnabled);
|
|
return;
|
|
}
|
|
|
|
// Configure our next frame callback.
|
|
SetupFrameCallback(lock);
|
|
|
|
int64_t tick = BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime);
|
|
const auto callbackTimeStamp = TimeStamp::FromSystemTime(tick);
|
|
const auto now = TimeStamp::Now();
|
|
|
|
// If the callback timestamp is close enough to our timestamp, use it,
|
|
// otherwise use the current time.
|
|
const TimeStamp& vsyncTimestamp =
|
|
std::abs((now - callbackTimeStamp).ToMilliseconds()) < 50.0
|
|
? callbackTimeStamp
|
|
: now;
|
|
|
|
CalculateVsyncRate(lock, vsyncTimestamp);
|
|
mLastVsyncTimeStamp = vsyncTimestamp;
|
|
const TimeStamp outputTimestamp = vsyncTimestamp + 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::CalculateVsyncRate(const MutexAutoLock& aProofOfLock,
|
|
TimeStamp aVsyncTimestamp) {
|
|
mMutex.AssertCurrentThreadOwns();
|
|
|
|
double duration = (aVsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
|
|
double curVsyncRate = mVsyncRate.ToMilliseconds();
|
|
|
|
LOG("WaylandVsyncSource::CalculateVsyncRate 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);
|
|
}
|
|
|
|
void WaylandVsyncSource::EnableVsync() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
LOG("WaylandVsyncSource::EnableVsync fps %f\n", GetFPS(mVsyncRate));
|
|
if (mVsyncEnabled || mIsShutdown) {
|
|
LOG(" early quit");
|
|
return;
|
|
}
|
|
mVsyncEnabled = true;
|
|
Refresh(lock);
|
|
}
|
|
|
|
void WaylandVsyncSource::DisableVsync() {
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
LOG("WaylandVsyncSource::DisableVsync fps %f\n", GetFPS(mVsyncRate));
|
|
mVsyncEnabled = false;
|
|
mCallbackRequested = false;
|
|
}
|
|
|
|
bool WaylandVsyncSource::IsVsyncEnabled() {
|
|
MutexAutoLock lock(mMutex);
|
|
return mVsyncEnabled;
|
|
}
|
|
|
|
void WaylandVsyncSource::Shutdown() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
LOG("WaylandVsyncSource::Shutdown fps %f\n", GetFPS(mVsyncRate));
|
|
mContainer = nullptr;
|
|
mNativeLayerRoot = nullptr;
|
|
mIsShutdown = true;
|
|
mVsyncEnabled = false;
|
|
mCallbackRequested = false;
|
|
MozClearHandleID(mIdleTimerID, g_source_remove);
|
|
MozClearPointer(mCallback, wl_callback_destroy);
|
|
}
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // MOZ_WAYLAND
|