1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 # include "WaylandVsyncSource.h"
10 # include "mozilla/UniquePtr.h"
11 # include "nsThreadUtils.h"
12 # include "nsISupportsImpl.h"
13 # include "MainThreadUtils.h"
14 # include "mozilla/ScopeExit.h"
15 # include "nsGtkUtils.h"
16 # include "mozilla/StaticPrefs_layout.h"
17 # include "mozilla/StaticPrefs_widget.h"
18 # include "nsWindow.h"
20 # include <gdk/gdkwayland.h>
23 # include "mozilla/Logging.h"
24 # include "nsTArray.h"
26 extern mozilla::LazyLogModule gWidgetVsync
;
28 # define LOG(str, ...) \
29 MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, \
30 ("[nsWindow %p]: " str, GetWindowForLogging(), ##__VA_ARGS__))
33 # endif /* MOZ_LOGGING */
35 using namespace mozilla::widget
;
39 static void WaylandVsyncSourceCallbackHandler(void* aData
,
40 struct wl_callback
* aCallback
,
42 RefPtr context
= static_cast<WaylandVsyncSource
*>(aData
);
43 context
->FrameCallback(aCallback
, aTime
);
46 static const struct wl_callback_listener WaylandVsyncSourceCallbackListener
= {
47 WaylandVsyncSourceCallbackHandler
};
49 static void NativeLayerRootWaylandVsyncCallback(void* aData
, uint32_t aTime
) {
50 RefPtr context
= static_cast<WaylandVsyncSource
*>(aData
);
51 context
->FrameCallback(nullptr, aTime
);
54 static float GetFPS(TimeDuration aVsyncRate
) {
55 return 1000.0f
/ float(aVsyncRate
.ToMilliseconds());
58 static nsTArray
<WaylandVsyncSource
*> gWaylandVsyncSources
;
60 Maybe
<TimeDuration
> WaylandVsyncSource::GetFastestVsyncRate() {
61 Maybe
<TimeDuration
> retVal
;
62 for (auto* source
: gWaylandVsyncSources
) {
63 auto rate
= source
->GetVsyncRateIfEnabled();
67 if (!retVal
.isSome()) {
68 retVal
.emplace(*rate
);
69 } else if (*rate
< *retVal
) {
77 WaylandVsyncSource::WaylandVsyncSource(nsWindow
* aWindow
)
78 : mMutex("WaylandVsyncSource"),
79 mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
80 mLastVsyncTimeStamp(TimeStamp::Now()),
82 mIdleTimeout(1000 / StaticPrefs::layout_throttled_frame_rate()) {
83 MOZ_ASSERT(NS_IsMainThread());
84 gWaylandVsyncSources
.AppendElement(this);
87 WaylandVsyncSource::~WaylandVsyncSource() {
88 gWaylandVsyncSources
.RemoveElement(this);
91 void WaylandVsyncSource::MaybeUpdateSource(MozContainer
* aContainer
) {
92 MutexAutoLock
lock(mMutex
);
94 LOG("WaylandVsyncSource::MaybeUpdateSource fps %f", GetFPS(mVsyncRate
));
96 if (aContainer
== mContainer
) {
97 LOG(" mContainer is the same, quit.");
101 mNativeLayerRoot
= nullptr;
102 mContainer
= aContainer
;
104 if (mMonitorEnabled
) {
105 LOG(" monitor enabled, ask for Refresh()");
106 mCallbackRequested
= false;
111 void WaylandVsyncSource::MaybeUpdateSource(
112 const RefPtr
<NativeLayerRootWayland
>& aNativeLayerRoot
) {
113 MutexAutoLock
lock(mMutex
);
115 LOG("WaylandVsyncSource::MaybeUpdateSource aNativeLayerRoot fps %f",
118 if (aNativeLayerRoot
== mNativeLayerRoot
) {
119 LOG(" mNativeLayerRoot is the same, quit.");
123 mNativeLayerRoot
= aNativeLayerRoot
;
124 mContainer
= nullptr;
126 if (mMonitorEnabled
) {
127 LOG(" monitor enabled, ask for Refresh()");
128 mCallbackRequested
= false;
133 void WaylandVsyncSource::Refresh(const MutexAutoLock
& aProofOfLock
) {
134 mMutex
.AssertCurrentThreadOwns();
136 LOG("WaylandVsyncSource::Refresh fps %f\n", GetFPS(mVsyncRate
));
138 if (!(mContainer
|| mNativeLayerRoot
) || !mMonitorEnabled
|| !mVsyncEnabled
||
139 mCallbackRequested
) {
140 // We don't need to do anything because:
141 // * We are unwanted by our widget or monitor, or
142 // * The last frame callback hasn't yet run to see that it had been shut
143 // down, so we can reuse it after having set mVsyncEnabled to true.
144 LOG(" quit mContainer %d mNativeLayerRoot %d mMonitorEnabled %d "
145 "mVsyncEnabled %d mCallbackRequested %d",
146 !!mContainer
, !!mNativeLayerRoot
, mMonitorEnabled
, mVsyncEnabled
,
147 !!mCallbackRequested
);
152 MozContainerSurfaceLock
lock(mContainer
);
153 struct wl_surface
* surface
= lock
.GetSurface();
154 LOG(" refresh from mContainer, wl_surface %p", surface
);
156 LOG(" we're missing wl_surface, register Refresh() callback");
157 // The surface hasn't been created yet. Try again when the surface is
159 RefPtr
<WaylandVsyncSource
> self(this);
160 moz_container_wayland_add_initial_draw_callback_locked(
161 mContainer
, [self
]() -> void {
162 MutexAutoLock
lock(self
->mMutex
);
169 // Vsync is enabled, but we don't have a callback configured. Set one up so
170 // we can get to work.
171 SetupFrameCallback(aProofOfLock
);
172 const TimeStamp lastVSync
= TimeStamp::Now();
173 mLastVsyncTimeStamp
= lastVSync
;
174 TimeStamp outputTimestamp
= mLastVsyncTimeStamp
+ mVsyncRate
;
177 MutexAutoUnlock
unlock(mMutex
);
178 NotifyVsync(lastVSync
, outputTimestamp
);
182 void WaylandVsyncSource::EnableMonitor() {
183 LOG("WaylandVsyncSource::EnableMonitor");
184 MutexAutoLock
lock(mMutex
);
185 if (mMonitorEnabled
) {
188 mMonitorEnabled
= true;
192 void WaylandVsyncSource::DisableMonitor() {
193 LOG("WaylandVsyncSource::DisableMonitor");
194 MutexAutoLock
lock(mMutex
);
195 if (!mMonitorEnabled
) {
198 mMonitorEnabled
= false;
199 mCallbackRequested
= false;
202 void WaylandVsyncSource::SetupFrameCallback(const MutexAutoLock
& aProofOfLock
) {
203 mMutex
.AssertCurrentThreadOwns();
204 MOZ_ASSERT(!mCallbackRequested
);
206 LOG("WaylandVsyncSource::SetupFrameCallback");
208 if (mNativeLayerRoot
) {
209 LOG(" use mNativeLayerRoot");
210 mNativeLayerRoot
->RequestFrameCallback(&NativeLayerRootWaylandVsyncCallback
,
213 MozContainerSurfaceLock
lock(mContainer
);
214 struct wl_surface
* surface
= lock
.GetSurface();
215 LOG(" use mContainer, wl_surface %p", surface
);
217 // We don't have a surface, either due to being called before it was made
218 // available in the mozcontainer, or after it was destroyed. We're all
220 LOG(" missing wl_surface, quit.");
224 LOG(" register frame callback");
225 MozClearPointer(mCallback
, wl_callback_destroy
);
226 mCallback
= wl_surface_frame(surface
);
227 wl_callback_add_listener(mCallback
, &WaylandVsyncSourceCallbackListener
,
229 wl_surface_commit(surface
);
230 wl_display_flush(WaylandDisplayGet()->GetDisplay());
233 mIdleTimerID
= g_timeout_add(
235 [](void* data
) -> gint
{
236 RefPtr vsync
= static_cast<WaylandVsyncSource
*>(data
);
237 if (vsync
->IdleCallback()) {
238 // We want to fire again, so don't clear mIdleTimerID
239 return G_SOURCE_CONTINUE
;
241 // No need for g_source_remove, caller does it for us.
242 vsync
->mIdleTimerID
= 0;
243 return G_SOURCE_REMOVE
;
249 mCallbackRequested
= true;
252 bool WaylandVsyncSource::IdleCallback() {
253 LOG("WaylandVsyncSource::IdleCallback");
254 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
256 RefPtr
<nsWindow
> window
;
258 TimeStamp outputTimestamp
;
260 MutexAutoLock
lock(mMutex
);
262 if (!mVsyncEnabled
|| !mMonitorEnabled
) {
263 // We are unwanted by either our creator or our consumer, so we just stop
264 // here without setting up a new frame callback.
265 LOG(" quit, mVsyncEnabled %d mMonitorEnabled %d", mVsyncEnabled
,
270 const auto now
= TimeStamp::Now();
271 const auto timeSinceLastVSync
= now
- mLastVsyncTimeStamp
;
272 if (timeSinceLastVSync
.ToMilliseconds() < mIdleTimeout
) {
273 // We're not idle, we want to fire the timer again.
277 LOG(" fire idle vsync");
278 CalculateVsyncRate(lock
, now
);
279 mLastVsyncTimeStamp
= lastVSync
= now
;
281 outputTimestamp
= mLastVsyncTimeStamp
+ mVsyncRate
;
285 // This could disable vsync.
286 window
->NotifyOcclusionState(OcclusionState::OCCLUDED
);
288 if (window
->IsDestroyed()) {
291 // Make sure to fire vsync now even if we get disabled afterwards.
292 // This gives an opportunity to clean up after the visibility state change.
293 // FIXME: Do we really need to do this?
294 NotifyVsync(lastVSync
, outputTimestamp
);
295 return StaticPrefs::widget_wayland_vsync_keep_firing_at_idle();
298 void WaylandVsyncSource::FrameCallback(wl_callback
* aCallback
, uint32_t aTime
) {
299 LOG("WaylandVsyncSource::FrameCallback");
300 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
303 // This might enable vsync.
304 RefPtr window
= mWindow
;
305 window
->NotifyOcclusionState(OcclusionState::VISIBLE
);
306 // NotifyOcclusionState can destroy us.
307 if (window
->IsDestroyed()) {
312 MutexAutoLock
lock(mMutex
);
313 mCallbackRequested
= false;
315 // NotifyOcclusionState() can clear and create new mCallback by
316 // EnableVsync()/Refresh(). So don't delete newly created frame callback.
317 if (aCallback
&& aCallback
== mCallback
) {
318 MozClearPointer(mCallback
, wl_callback_destroy
);
321 if (!mVsyncEnabled
|| !mMonitorEnabled
) {
322 // We are unwanted by either our creator or our consumer, so we just stop
323 // here without setting up a new frame callback.
324 LOG(" quit, mVsyncEnabled %d mMonitorEnabled %d", mVsyncEnabled
,
329 // Configure our next frame callback.
330 SetupFrameCallback(lock
);
332 int64_t tick
= BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime
);
333 const auto callbackTimeStamp
= TimeStamp::FromSystemTime(tick
);
334 const auto now
= TimeStamp::Now();
336 // If the callback timestamp is close enough to our timestamp, use it,
337 // otherwise use the current time.
338 const TimeStamp
& vsyncTimestamp
=
339 std::abs((now
- callbackTimeStamp
).ToMilliseconds()) < 50.0
343 CalculateVsyncRate(lock
, vsyncTimestamp
);
344 mLastVsyncTimeStamp
= vsyncTimestamp
;
345 const TimeStamp outputTimestamp
= vsyncTimestamp
+ mVsyncRate
;
348 MutexAutoUnlock
unlock(mMutex
);
349 NotifyVsync(vsyncTimestamp
, outputTimestamp
);
353 TimeDuration
WaylandVsyncSource::GetVsyncRate() {
354 MutexAutoLock
lock(mMutex
);
358 Maybe
<TimeDuration
> WaylandVsyncSource::GetVsyncRateIfEnabled() {
359 MutexAutoLock
lock(mMutex
);
360 if (!mVsyncEnabled
) {
363 return Some(mVsyncRate
);
366 void WaylandVsyncSource::CalculateVsyncRate(const MutexAutoLock
& aProofOfLock
,
367 TimeStamp aVsyncTimestamp
) {
368 mMutex
.AssertCurrentThreadOwns();
370 double duration
= (aVsyncTimestamp
- mLastVsyncTimeStamp
).ToMilliseconds();
371 double curVsyncRate
= mVsyncRate
.ToMilliseconds();
373 LOG("WaylandVsyncSource::CalculateVsyncRate start fps %f\n",
377 if (duration
> curVsyncRate
) {
378 correction
= fmin(curVsyncRate
, (duration
- curVsyncRate
) / 10);
379 mVsyncRate
+= TimeDuration::FromMilliseconds(correction
);
381 correction
= fmin(curVsyncRate
/ 2, (curVsyncRate
- duration
) / 10);
382 mVsyncRate
-= TimeDuration::FromMilliseconds(correction
);
385 LOG(" new fps %f correction %f\n", GetFPS(mVsyncRate
), correction
);
388 void WaylandVsyncSource::EnableVsync() {
389 MOZ_ASSERT(NS_IsMainThread());
391 MutexAutoLock
lock(mMutex
);
393 LOG("WaylandVsyncSource::EnableVsync fps %f\n", GetFPS(mVsyncRate
));
394 if (mVsyncEnabled
|| mIsShutdown
) {
398 mVsyncEnabled
= true;
402 void WaylandVsyncSource::DisableVsync() {
403 MutexAutoLock
lock(mMutex
);
405 LOG("WaylandVsyncSource::DisableVsync fps %f\n", GetFPS(mVsyncRate
));
406 mVsyncEnabled
= false;
407 mCallbackRequested
= false;
410 bool WaylandVsyncSource::IsVsyncEnabled() {
411 MutexAutoLock
lock(mMutex
);
412 return mVsyncEnabled
;
415 void WaylandVsyncSource::Shutdown() {
416 MOZ_ASSERT(NS_IsMainThread());
417 MutexAutoLock
lock(mMutex
);
419 LOG("WaylandVsyncSource::Shutdown fps %f\n", GetFPS(mVsyncRate
));
420 mContainer
= nullptr;
421 mNativeLayerRoot
= nullptr;
423 mVsyncEnabled
= false;
424 mCallbackRequested
= false;
425 MozClearHandleID(mIdleTimerID
, g_source_remove
);
426 MozClearPointer(mCallback
, wl_callback_destroy
);
429 } // namespace mozilla
431 #endif // MOZ_WAYLAND