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/. */
6 
7 #ifdef MOZ_WAYLAND
8 
9 #  include "WaylandVsyncSource.h"
10 #  include "nsThreadUtils.h"
11 #  include "nsISupportsImpl.h"
12 #  include "MainThreadUtils.h"
13 
14 #  include <gdk/gdkwayland.h>
15 
16 using namespace mozilla::widget;
17 
18 namespace mozilla {
19 
WaylandVsyncSourceCallbackHandler(void * aData,struct wl_callback * aCallback,uint32_t aTime)20 static void WaylandVsyncSourceCallbackHandler(void* aData,
21                                               struct wl_callback* aCallback,
22                                               uint32_t aTime) {
23   WaylandVsyncSource::WaylandDisplay* context =
24       (WaylandVsyncSource::WaylandDisplay*)aData;
25   wl_callback_destroy(aCallback);
26   context->FrameCallback(aTime);
27 }
28 
WaylandVsyncSourceCallbackHandler(void * aData,uint32_t aTime)29 static void WaylandVsyncSourceCallbackHandler(void* aData, uint32_t aTime) {
30   WaylandVsyncSource::WaylandDisplay* context =
31       (WaylandVsyncSource::WaylandDisplay*)aData;
32   context->FrameCallback(aTime);
33 }
34 
35 static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
36     WaylandVsyncSourceCallbackHandler};
37 
WaylandDisplay()38 WaylandVsyncSource::WaylandDisplay::WaylandDisplay()
39     : mMutex("WaylandVsyncSource"),
40       mIsShutdown(false),
41       mVsyncEnabled(false),
42       mMonitorEnabled(false),
43       mCallbackRequested(false),
44       mDisplay(WaylandDisplayGetWLDisplay()),
45       mContainer(nullptr),
46       mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
47       mLastVsyncTimeStamp(TimeStamp::Now()) {
48   MOZ_ASSERT(NS_IsMainThread());
49 }
50 
MaybeUpdateSource(MozContainer * aContainer)51 void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource(
52     MozContainer* aContainer) {
53   MutexAutoLock lock(mMutex);
54 
55   if (aContainer == mContainer) {
56     return;
57   }
58 
59   mNativeLayerRoot = nullptr;
60   mContainer = aContainer;
61 
62   if (mMonitorEnabled) {
63     mCallbackRequested = false;
64     Refresh();
65   }
66 }
67 
MaybeUpdateSource(const RefPtr<NativeLayerRootWayland> & aNativeLayerRoot)68 void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource(
69     const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot) {
70   MutexAutoLock lock(mMutex);
71 
72   if (aNativeLayerRoot == mNativeLayerRoot) {
73     return;
74   }
75 
76   mNativeLayerRoot = aNativeLayerRoot;
77   mContainer = nullptr;
78 
79   if (mMonitorEnabled) {
80     mCallbackRequested = false;
81     Refresh();
82   }
83 }
84 
Refresh()85 void WaylandVsyncSource::WaylandDisplay::Refresh() {
86   mMutex.AssertCurrentThreadOwns();
87 
88   if (!(mContainer || mNativeLayerRoot) || !mMonitorEnabled || !mVsyncEnabled ||
89       mCallbackRequested) {
90     // We don't need to do anything because:
91     // * We are unwanted by our widget or monitor, or
92     // * The last frame callback hasn't yet run to see that it had been shut
93     //   down, so we can reuse it after having set mVsyncEnabled to true.
94     return;
95   }
96 
97   if (mContainer) {
98     struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
99     if (!surface) {
100       // The surface hasn't been created yet. Try again when the surface is
101       // ready.
102       RefPtr<WaylandVsyncSource::WaylandDisplay> self(this);
103       moz_container_wayland_add_initial_draw_callback(
104           mContainer, [self]() -> void {
105             MutexAutoLock lock(self->mMutex);
106             self->Refresh();
107           });
108       return;
109     }
110     moz_container_wayland_surface_unlock(mContainer, &surface);
111   }
112 
113   // Vsync is enabled, but we don't have a callback configured. Set one up so
114   // we can get to work.
115   SetupFrameCallback();
116   mLastVsyncTimeStamp = TimeStamp::Now();
117   TimeStamp outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate();
118 
119   {
120     MutexAutoUnlock unlock(mMutex);
121     NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
122   }
123 }
124 
EnableMonitor()125 void WaylandVsyncSource::WaylandDisplay::EnableMonitor() {
126   MutexAutoLock lock(mMutex);
127   if (mMonitorEnabled) {
128     return;
129   }
130   mMonitorEnabled = true;
131   Refresh();
132 }
133 
DisableMonitor()134 void WaylandVsyncSource::WaylandDisplay::DisableMonitor() {
135   MutexAutoLock lock(mMutex);
136   if (!mMonitorEnabled) {
137     return;
138   }
139   mMonitorEnabled = false;
140   mCallbackRequested = false;
141 }
142 
SetupFrameCallback()143 void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback() {
144   mMutex.AssertCurrentThreadOwns();
145   MOZ_ASSERT(!mCallbackRequested);
146 
147   if (mNativeLayerRoot) {
148     mNativeLayerRoot->RequestFrameCallback(&WaylandVsyncSourceCallbackHandler,
149                                            this);
150   } else {
151     struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
152     if (!surface) {
153       // We don't have a surface, either due to being called before it was made
154       // available in the mozcontainer, or after it was destroyed. We're all
155       // done regardless.
156       mCallbackRequested = false;
157       return;
158     }
159 
160     wl_callback* callback = wl_surface_frame(surface);
161     wl_callback_add_listener(callback, &WaylandVsyncSourceCallbackListener,
162                              this);
163     wl_surface_commit(surface);
164     wl_display_flush(mDisplay);
165     moz_container_wayland_surface_unlock(mContainer, &surface);
166   }
167 
168   mCallbackRequested = true;
169 }
170 
FrameCallback(uint32_t timestampTime)171 void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t timestampTime) {
172   MutexAutoLock lock(mMutex);
173   mCallbackRequested = false;
174 
175   if (!mVsyncEnabled || !mMonitorEnabled) {
176     // We are unwanted by either our creator or our consumer, so we just stop
177     // here without setting up a new frame callback.
178     return;
179   }
180 
181   // Configure our next frame callback.
182   SetupFrameCallback();
183 
184   int64_t tick =
185       BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
186   TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick);
187   double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds();
188 
189   TimeStamp vsyncTimestamp;
190   if (duration < 50 && duration > -50) {
191     vsyncTimestamp = callbackTimeStamp;
192   } else {
193     vsyncTimestamp = TimeStamp::Now();
194   }
195 
196   CalculateVsyncRate(vsyncTimestamp);
197   mLastVsyncTimeStamp = vsyncTimestamp;
198   TimeStamp outputTimestamp = vsyncTimestamp + GetVsyncRate();
199 
200   {
201     MutexAutoUnlock unlock(mMutex);
202     NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
203   }
204 }
205 
GetVsyncRate()206 TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() {
207   return mVsyncRate;
208 }
209 
CalculateVsyncRate(TimeStamp vsyncTimestamp)210 void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate(
211     TimeStamp vsyncTimestamp) {
212   double duration = (vsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
213   double curVsyncRate = mVsyncRate.ToMilliseconds();
214   double correction;
215 
216   if (duration > curVsyncRate) {
217     correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
218     mVsyncRate += TimeDuration::FromMilliseconds(correction);
219   } else {
220     correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10);
221     mVsyncRate -= TimeDuration::FromMilliseconds(correction);
222   }
223 }
224 
EnableVsync()225 void WaylandVsyncSource::WaylandDisplay::EnableVsync() {
226   MOZ_ASSERT(NS_IsMainThread());
227   MutexAutoLock lock(mMutex);
228   if (mVsyncEnabled || mIsShutdown) {
229     return;
230   }
231   mVsyncEnabled = true;
232   Refresh();
233 }
234 
DisableVsync()235 void WaylandVsyncSource::WaylandDisplay::DisableVsync() {
236   MutexAutoLock lock(mMutex);
237   mVsyncEnabled = false;
238   mCallbackRequested = false;
239 }
240 
IsVsyncEnabled()241 bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() {
242   MutexAutoLock lock(mMutex);
243   return mVsyncEnabled;
244 }
245 
Shutdown()246 void WaylandVsyncSource::WaylandDisplay::Shutdown() {
247   MOZ_ASSERT(NS_IsMainThread());
248   MutexAutoLock lock(mMutex);
249   mIsShutdown = true;
250   mVsyncEnabled = false;
251   mCallbackRequested = false;
252 }
253 
254 }  // namespace mozilla
255 
256 #endif  // MOZ_WAYLAND
257