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