no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / widget / gtk / WaylandVsyncSource.cpp
blobe5bca04837a9d0a1de3ee766f1e3310968befa68
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/. */
7 #ifdef MOZ_WAYLAND
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>
22 # ifdef MOZ_LOGGING
23 # include "mozilla/Logging.h"
24 # include "nsTArray.h"
25 # include "Units.h"
26 extern mozilla::LazyLogModule gWidgetVsync;
27 # undef LOG
28 # define LOG(str, ...) \
29 MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, \
30 ("[nsWindow %p]: " str, GetWindowForLogging(), ##__VA_ARGS__))
31 # else
32 # define LOG(...)
33 # endif /* MOZ_LOGGING */
35 using namespace mozilla::widget;
37 namespace mozilla {
39 static void WaylandVsyncSourceCallbackHandler(void* aData,
40 struct wl_callback* aCallback,
41 uint32_t aTime) {
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();
64 if (!rate) {
65 continue;
67 if (!retVal.isSome()) {
68 retVal.emplace(*rate);
69 } else if (*rate < *retVal) {
70 retVal.ref() = *rate;
74 return retVal;
77 WaylandVsyncSource::WaylandVsyncSource(nsWindow* aWindow)
78 : mMutex("WaylandVsyncSource"),
79 mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
80 mLastVsyncTimeStamp(TimeStamp::Now()),
81 mWindow(aWindow),
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.");
98 return;
101 mNativeLayerRoot = nullptr;
102 mContainer = aContainer;
104 if (mMonitorEnabled) {
105 LOG(" monitor enabled, ask for Refresh()");
106 mCallbackRequested = false;
107 Refresh(lock);
111 void WaylandVsyncSource::MaybeUpdateSource(
112 const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot) {
113 MutexAutoLock lock(mMutex);
115 LOG("WaylandVsyncSource::MaybeUpdateSource aNativeLayerRoot fps %f",
116 GetFPS(mVsyncRate));
118 if (aNativeLayerRoot == mNativeLayerRoot) {
119 LOG(" mNativeLayerRoot is the same, quit.");
120 return;
123 mNativeLayerRoot = aNativeLayerRoot;
124 mContainer = nullptr;
126 if (mMonitorEnabled) {
127 LOG(" monitor enabled, ask for Refresh()");
128 mCallbackRequested = false;
129 Refresh(lock);
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);
148 return;
151 if (mContainer) {
152 MozContainerSurfaceLock lock(mContainer);
153 struct wl_surface* surface = lock.GetSurface();
154 LOG(" refresh from mContainer, wl_surface %p", surface);
155 if (!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
158 // ready.
159 RefPtr<WaylandVsyncSource> self(this);
160 moz_container_wayland_add_initial_draw_callback_locked(
161 mContainer, [self]() -> void {
162 MutexAutoLock lock(self->mMutex);
163 self->Refresh(lock);
165 return;
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) {
186 return;
188 mMonitorEnabled = true;
189 Refresh(lock);
192 void WaylandVsyncSource::DisableMonitor() {
193 LOG("WaylandVsyncSource::DisableMonitor");
194 MutexAutoLock lock(mMutex);
195 if (!mMonitorEnabled) {
196 return;
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,
211 this);
212 } else {
213 MozContainerSurfaceLock lock(mContainer);
214 struct wl_surface* surface = lock.GetSurface();
215 LOG(" use mContainer, wl_surface %p", surface);
216 if (!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
219 // done regardless.
220 LOG(" missing wl_surface, quit.");
221 return;
224 LOG(" register frame callback");
225 MozClearPointer(mCallback, wl_callback_destroy);
226 mCallback = wl_surface_frame(surface);
227 wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
228 this);
229 wl_surface_commit(surface);
230 wl_display_flush(WaylandDisplayGet()->GetDisplay());
232 if (!mIdleTimerID) {
233 mIdleTimerID = g_timeout_add(
234 mIdleTimeout,
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;
245 this);
249 mCallbackRequested = true;
252 bool WaylandVsyncSource::IdleCallback() {
253 LOG("WaylandVsyncSource::IdleCallback");
254 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
256 RefPtr<nsWindow> window;
257 TimeStamp lastVSync;
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,
266 mMonitorEnabled);
267 return false;
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.
274 return true;
277 LOG(" fire idle vsync");
278 CalculateVsyncRate(lock, now);
279 mLastVsyncTimeStamp = lastVSync = now;
281 outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
282 window = mWindow;
285 // This could disable vsync.
286 window->NotifyOcclusionState(OcclusionState::OCCLUDED);
288 if (window->IsDestroyed()) {
289 return false;
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()) {
308 return;
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,
325 mMonitorEnabled);
326 return;
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
340 ? callbackTimeStamp
341 : now;
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);
355 return mVsyncRate;
358 Maybe<TimeDuration> WaylandVsyncSource::GetVsyncRateIfEnabled() {
359 MutexAutoLock lock(mMutex);
360 if (!mVsyncEnabled) {
361 return Nothing();
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",
374 GetFPS(mVsyncRate));
376 double correction;
377 if (duration > curVsyncRate) {
378 correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
379 mVsyncRate += TimeDuration::FromMilliseconds(correction);
380 } else {
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) {
395 LOG(" early quit");
396 return;
398 mVsyncEnabled = true;
399 Refresh(lock);
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;
422 mIsShutdown = true;
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