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