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 "mozilla/UniquePtr.h"
11 #  include "nsThreadUtils.h"
12 #  include "nsISupportsImpl.h"
13 #  include "MainThreadUtils.h"
14 
15 #  include <gdk/gdkwayland.h>
16 
17 #  ifdef MOZ_LOGGING
18 #    include "mozilla/Logging.h"
19 #    include "nsTArray.h"
20 #    include "Units.h"
21 extern mozilla::LazyLogModule gWidgetVsync;
22 #    define LOG(...) \
23       MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, (__VA_ARGS__))
24 #  else
25 #    define LOG(...)
26 #  endif /* MOZ_LOGGING */
27 
28 using namespace mozilla::widget;
29 
30 namespace mozilla {
31 
WaylandVsyncSourceCallbackHandler(void * aData,struct wl_callback * aCallback,uint32_t aTime)32 static void WaylandVsyncSourceCallbackHandler(void* aData,
33                                               struct wl_callback* aCallback,
34                                               uint32_t aTime) {
35   WaylandVsyncSource::WaylandDisplay* context =
36       (WaylandVsyncSource::WaylandDisplay*)aData;
37   wl_callback_destroy(aCallback);
38   context->FrameCallback(aTime);
39 }
40 
WaylandVsyncSourceCallbackHandler(void * aData,uint32_t aTime)41 static void WaylandVsyncSourceCallbackHandler(void* aData, uint32_t aTime) {
42   WaylandVsyncSource::WaylandDisplay* context =
43       (WaylandVsyncSource::WaylandDisplay*)aData;
44   context->FrameCallback(aTime);
45 }
46 
47 static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
48     WaylandVsyncSourceCallbackHandler};
49 
GetFPS(TimeDuration aVsyncRate)50 static float GetFPS(TimeDuration aVsyncRate) {
51   return 1000.0 / aVsyncRate.ToMilliseconds();
52 }
53 
54 static UniquePtr<LinkedList<WaylandVsyncSource::WaylandDisplay>>
55     gWaylandDisplays;
56 
GetFastestVsyncRate()57 Maybe<TimeDuration> WaylandVsyncSource::GetFastestVsyncRate() {
58   Maybe<TimeDuration> retVal;
59   if (gWaylandDisplays) {
60     for (auto* display : *gWaylandDisplays) {
61       if (display->IsVsyncEnabled()) {
62         TimeDuration rate = display->GetVsyncRate();
63         if (!retVal.isSome()) {
64           retVal.emplace(rate);
65         } else if (rate < *retVal) {
66           retVal.ref() = rate;
67         }
68       }
69     }
70   }
71 
72   return retVal;
73 }
74 
WaylandDisplay()75 WaylandVsyncSource::WaylandDisplay::WaylandDisplay()
76     : mMutex("WaylandVsyncSource"),
77       mIsShutdown(false),
78       mVsyncEnabled(false),
79       mMonitorEnabled(false),
80       mCallbackRequested(false),
81       mContainer(nullptr),
82       mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
83       mLastVsyncTimeStamp(TimeStamp::Now()) {
84   MOZ_ASSERT(NS_IsMainThread());
85 
86   if (!gWaylandDisplays) {
87     gWaylandDisplays =
88         MakeUnique<LinkedList<WaylandVsyncSource::WaylandDisplay>>();
89   }
90   gWaylandDisplays->insertBack(this);
91 }
92 
~WaylandDisplay()93 WaylandVsyncSource::WaylandDisplay::~WaylandDisplay() {
94   remove();  // Remove from the linked list.
95   if (gWaylandDisplays->isEmpty()) {
96     gWaylandDisplays = nullptr;
97   }
98 }
99 
MaybeUpdateSource(MozContainer * aContainer)100 void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource(
101     MozContainer* aContainer) {
102   MutexAutoLock lock(mMutex);
103 
104   LOG("WaylandVsyncSource::MaybeUpdateSource mContainer (nsWindow %p) fps %f",
105       aContainer ? moz_container_get_nsWindow(aContainer) : nullptr,
106       GetFPS(mVsyncRate));
107 
108   if (aContainer == mContainer) {
109     LOG("  mContainer is the same, quit.");
110     return;
111   }
112 
113   mNativeLayerRoot = nullptr;
114   mContainer = aContainer;
115 
116   if (mMonitorEnabled) {
117     LOG("  monitor enabled, ask for Refresh()");
118     mCallbackRequested = false;
119     Refresh(lock);
120   }
121 }
122 
MaybeUpdateSource(const RefPtr<NativeLayerRootWayland> & aNativeLayerRoot)123 void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource(
124     const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot) {
125   MutexAutoLock lock(mMutex);
126 
127   LOG("WaylandVsyncSource::MaybeUpdateSource aNativeLayerRoot fps %f",
128       GetFPS(mVsyncRate));
129 
130   if (aNativeLayerRoot == mNativeLayerRoot) {
131     LOG("  mNativeLayerRoot is the same, quit.");
132     return;
133   }
134 
135   mNativeLayerRoot = aNativeLayerRoot;
136   mContainer = nullptr;
137 
138   if (mMonitorEnabled) {
139     LOG("  monitor enabled, ask for Refresh()");
140     mCallbackRequested = false;
141     Refresh(lock);
142   }
143 }
144 
Refresh(const MutexAutoLock & aProofOfLock)145 void WaylandVsyncSource::WaylandDisplay::Refresh(
146     const MutexAutoLock& aProofOfLock) {
147   LOG("WaylandDisplay::Refresh fps %f\n", GetFPS(mVsyncRate));
148 
149   if (!(mContainer || mNativeLayerRoot) || !mMonitorEnabled || !mVsyncEnabled ||
150       mCallbackRequested) {
151     // We don't need to do anything because:
152     // * We are unwanted by our widget or monitor, or
153     // * The last frame callback hasn't yet run to see that it had been shut
154     //   down, so we can reuse it after having set mVsyncEnabled to true.
155     LOG("  quit mContainer %d mNativeLayerRoot %d mMonitorEnabled %d "
156         "mVsyncEnabled %d mCallbackRequested %d",
157         !!mContainer, !!mNativeLayerRoot, mMonitorEnabled, mVsyncEnabled,
158         !!mCallbackRequested);
159     return;
160   }
161 
162   if (mContainer) {
163     struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
164     LOG("  refresh from mContainer, wl_surface %p", surface);
165     if (!surface) {
166       LOG("  we're missing wl_surface, register Refresh() callback");
167       // The surface hasn't been created yet. Try again when the surface is
168       // ready.
169       RefPtr<WaylandVsyncSource::WaylandDisplay> self(this);
170       moz_container_wayland_add_initial_draw_callback(
171           mContainer, [self]() -> void {
172             MutexAutoLock lock(self->mMutex);
173             self->Refresh(lock);
174           });
175       return;
176     }
177     moz_container_wayland_surface_unlock(mContainer, &surface);
178   }
179 
180   // Vsync is enabled, but we don't have a callback configured. Set one up so
181   // we can get to work.
182   SetupFrameCallback(aProofOfLock);
183   mLastVsyncTimeStamp = TimeStamp::Now();
184   TimeStamp outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate();
185 
186   {
187     MutexAutoUnlock unlock(mMutex);
188     NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
189   }
190 }
191 
EnableMonitor()192 void WaylandVsyncSource::WaylandDisplay::EnableMonitor() {
193   LOG("WaylandVsyncSource::EnableMonitor");
194   MutexAutoLock lock(mMutex);
195   if (mMonitorEnabled) {
196     return;
197   }
198   mMonitorEnabled = true;
199   Refresh(lock);
200 }
201 
DisableMonitor()202 void WaylandVsyncSource::WaylandDisplay::DisableMonitor() {
203   LOG("WaylandVsyncSource::DisableMonitor");
204   MutexAutoLock lock(mMutex);
205   if (!mMonitorEnabled) {
206     return;
207   }
208   mMonitorEnabled = false;
209   mCallbackRequested = false;
210 }
211 
SetupFrameCallback(const MutexAutoLock & aProofOfLock)212 void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback(
213     const MutexAutoLock& aProofOfLock) {
214   MOZ_ASSERT(!mCallbackRequested);
215 
216   LOG("WaylandVsyncSource::SetupFrameCallback");
217 
218   if (mNativeLayerRoot) {
219     LOG("  use mNativeLayerRoot");
220     mNativeLayerRoot->RequestFrameCallback(&WaylandVsyncSourceCallbackHandler,
221                                            this);
222   } else {
223     struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
224     LOG("  use mContainer, wl_surface %p", surface);
225     if (!surface) {
226       // We don't have a surface, either due to being called before it was made
227       // available in the mozcontainer, or after it was destroyed. We're all
228       // done regardless.
229       LOG("  missing wl_surface, quit.");
230       return;
231     }
232 
233     LOG("  register frame callback");
234     wl_callback* callback = wl_surface_frame(surface);
235     wl_callback_add_listener(callback, &WaylandVsyncSourceCallbackListener,
236                              this);
237     wl_surface_commit(surface);
238     wl_display_flush(WaylandDisplayGet()->GetDisplay());
239     moz_container_wayland_surface_unlock(mContainer, &surface);
240   }
241 
242   mCallbackRequested = true;
243 }
244 
FrameCallback(uint32_t aTime)245 void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t aTime) {
246   LOG("WaylandVsyncSource::FrameCallback");
247 
248   MutexAutoLock lock(mMutex);
249   mCallbackRequested = false;
250 
251   if (!mVsyncEnabled || !mMonitorEnabled) {
252     // We are unwanted by either our creator or our consumer, so we just stop
253     // here without setting up a new frame callback.
254     LOG("  quit, mVsyncEnabled %d mMonitorEnabled %d", mVsyncEnabled,
255         mMonitorEnabled);
256     return;
257   }
258 
259   // Configure our next frame callback.
260   SetupFrameCallback(lock);
261 
262   int64_t tick = BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime);
263   TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick);
264   double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds();
265 
266   TimeStamp vsyncTimestamp;
267   if (duration < 50 && duration > -50) {
268     vsyncTimestamp = callbackTimeStamp;
269   } else {
270     vsyncTimestamp = TimeStamp::Now();
271   }
272 
273   CalculateVsyncRate(lock, vsyncTimestamp);
274   mLastVsyncTimeStamp = vsyncTimestamp;
275   TimeStamp outputTimestamp = vsyncTimestamp + GetVsyncRate();
276 
277   {
278     MutexAutoUnlock unlock(mMutex);
279     NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
280   }
281 }
282 
GetVsyncRate()283 TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() {
284   return mVsyncRate;
285 }
286 
CalculateVsyncRate(const MutexAutoLock & aProofOfLock,TimeStamp aVsyncTimestamp)287 void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate(
288     const MutexAutoLock& aProofOfLock, TimeStamp aVsyncTimestamp) {
289   double duration = (aVsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
290   double curVsyncRate = mVsyncRate.ToMilliseconds();
291 
292   LOG("WaylandDisplay::CalculateVsyncRate start fps %f\n", GetFPS(mVsyncRate));
293 
294   double correction;
295   if (duration > curVsyncRate) {
296     correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
297     mVsyncRate += TimeDuration::FromMilliseconds(correction);
298   } else {
299     correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10);
300     mVsyncRate -= TimeDuration::FromMilliseconds(correction);
301   }
302 
303   LOG("  new fps %f correction %f\n", GetFPS(mVsyncRate), correction);
304 }
305 
EnableVsync()306 void WaylandVsyncSource::WaylandDisplay::EnableVsync() {
307   MOZ_ASSERT(NS_IsMainThread());
308 
309   LOG("WaylandVsyncSource::EnableVsync fps %f\n", GetFPS(mVsyncRate));
310   MutexAutoLock lock(mMutex);
311   if (mVsyncEnabled || mIsShutdown) {
312     LOG("  early quit");
313     return;
314   }
315   mVsyncEnabled = true;
316   Refresh(lock);
317 }
318 
DisableVsync()319 void WaylandVsyncSource::WaylandDisplay::DisableVsync() {
320   LOG("WaylandVsyncSource::DisableVsync fps %f\n", GetFPS(mVsyncRate));
321   MutexAutoLock lock(mMutex);
322   mVsyncEnabled = false;
323   mCallbackRequested = false;
324 }
325 
IsVsyncEnabled()326 bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() {
327   MutexAutoLock lock(mMutex);
328   return mVsyncEnabled;
329 }
330 
Shutdown()331 void WaylandVsyncSource::WaylandDisplay::Shutdown() {
332   MOZ_ASSERT(NS_IsMainThread());
333   LOG("WaylandVsyncSource::Shutdown fps %f\n", GetFPS(mVsyncRate));
334   MutexAutoLock lock(mMutex);
335   mContainer = nullptr;
336   mNativeLayerRoot = nullptr;
337   mIsShutdown = true;
338   mVsyncEnabled = false;
339   mCallbackRequested = false;
340 }
341 
342 }  // namespace mozilla
343 
344 #endif  // MOZ_WAYLAND
345