fune/widget/gtk/WaylandVsyncSource.cpp
Robert Mader a715823143 Bug 1670444 - [Wayland] Some vsyncsource improvements, r=stransky
This implements the following three things:
 - use the frame callback timestamp. While there are few guarantees about
its behaviour, it still seems to make sense to use it assuming: a) the compositor
uses monotonic system time (we do a simple sanity check) b) the compositor is
more likely to run at high priority, thus making the offset from the actual
vsync less jittery c) the timestamp is closer to the actual vsync event,
making it more fitting for how we use it internally
 - implement a very simplistic estimate of the refresh interval.
Since bug 1653737 WR takes an estimated next output time to optimize
for. Until now this was hardcoded to 16.6ms from the last `Now()`. Now
we adjust the value on each interval slightly, making it much more
precise on non-60Hz refresh rates (this certainly can get improved more)
 - Shuffle around mutex looking a bit, making sure we don't hold it
while calling `NotifyVsync()`. That should make it less likely to run
into deadlock conditions.

Depends on D95515

Differential Revision: https://phabricator.services.mozilla.com/D93169
2020-11-03 11:39:18 +00:00

214 lines
6.4 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 "nsThreadUtils.h"
# include "nsISupportsImpl.h"
# include "MainThreadUtils.h"
# include <gdk/gdkwayland.h>
using namespace mozilla::widget;
namespace mozilla {
static void WaylandVsyncSourceCallbackHandler(void* data,
struct wl_callback* callback,
uint32_t time) {
WaylandVsyncSource::WaylandDisplay* context =
(WaylandVsyncSource::WaylandDisplay*)data;
wl_callback_destroy(callback);
context->FrameCallback(time);
}
static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
WaylandVsyncSourceCallbackHandler};
WaylandVsyncSource::WaylandDisplay::WaylandDisplay(MozContainer* container)
: mEnabledLock("WaylandVsyncEnabledLock"),
mIsShutdown(false),
mVsyncEnabled(false),
mMonitorEnabled(false),
mCallback(nullptr),
mContainer(container),
mLastVsyncTimeStamp(TimeStamp::Now()) {
MOZ_ASSERT(NS_IsMainThread());
// We store the display here so all the frame callbacks won't have to look it
// up all the time.
mDisplay = WaylandDisplayGetWLDisplay();
mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
}
void WaylandVsyncSource::WaylandDisplay::ClearFrameCallback() {
if (mCallback) {
wl_callback_destroy(mCallback);
mCallback = nullptr;
}
}
void WaylandVsyncSource::WaylandDisplay::Refresh() {
TimeStamp outputTimestamp;
{
MutexAutoLock lock(mEnabledLock);
if (!mMonitorEnabled || !mVsyncEnabled || mCallback) {
// 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.
return;
}
struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
if (!surface) {
// The surface hasn't been created yet. Try again when the surface is
// ready.
RefPtr<WaylandVsyncSource::WaylandDisplay> self(this);
moz_container_wayland_add_initial_draw_callback(
mContainer, [self]() -> void { self->Refresh(); });
return;
}
moz_container_wayland_surface_unlock(mContainer, &surface);
// Vsync is enabled, but we don't have a callback configured. Set one up so
// we can get to work.
SetupFrameCallback();
mLastVsyncTimeStamp = TimeStamp::Now();
outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate();
}
NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
}
void WaylandVsyncSource::WaylandDisplay::EnableMonitor() {
{
MutexAutoLock lock(mEnabledLock);
if (mMonitorEnabled) {
return;
}
mMonitorEnabled = true;
}
Refresh();
}
void WaylandVsyncSource::WaylandDisplay::DisableMonitor() {
MutexAutoLock lock(mEnabledLock);
if (!mMonitorEnabled) {
return;
}
mMonitorEnabled = false;
ClearFrameCallback();
}
void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback() {
MOZ_ASSERT(mCallback == nullptr);
struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
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.
ClearFrameCallback();
return;
}
mCallback = wl_surface_frame(surface);
wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
this);
wl_surface_commit(surface);
wl_display_flush(mDisplay);
moz_container_wayland_surface_unlock(mContainer, &surface);
}
void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t timestampTime) {
TimeStamp outputTimestamp;
{
MutexAutoLock lock(mEnabledLock);
mCallback = nullptr;
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.
return;
}
// Configure our next frame callback.
SetupFrameCallback();
int64_t tick =
BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick);
double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds();
TimeStamp vsyncTimestamp;
if (duration < 50 && duration > -50) {
vsyncTimestamp = callbackTimeStamp;
} else {
vsyncTimestamp = TimeStamp::Now();
}
CalculateVsyncRate(vsyncTimestamp);
mLastVsyncTimeStamp = vsyncTimestamp;
outputTimestamp = vsyncTimestamp + GetVsyncRate();
}
NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
}
TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() {
return mVsyncRate;
}
void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate(
TimeStamp vsyncTimestamp) {
double duration = (vsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
double curVsyncRate = mVsyncRate.ToMilliseconds();
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);
}
}
void WaylandVsyncSource::WaylandDisplay::EnableVsync() {
MOZ_ASSERT(NS_IsMainThread());
{
MutexAutoLock lock(mEnabledLock);
if (mVsyncEnabled || mIsShutdown) {
return;
}
mVsyncEnabled = true;
}
Refresh();
}
void WaylandVsyncSource::WaylandDisplay::DisableVsync() {
MutexAutoLock lock(mEnabledLock);
mVsyncEnabled = false;
ClearFrameCallback();
}
bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() {
MutexAutoLock lock(mEnabledLock);
return mVsyncEnabled;
}
void WaylandVsyncSource::WaylandDisplay::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mEnabledLock);
mIsShutdown = true;
mVsyncEnabled = false;
ClearFrameCallback();
wl_display_roundtrip(mDisplay);
}
} // namespace mozilla
#endif // MOZ_WAYLAND