Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 ts=2 et 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 : #include "mozilla/layers/CompositorVsyncScheduler.h"
8 :
9 : #include <stdio.h> // for fprintf, stdout
10 : #include <stdint.h> // for uint64_t
11 : #include "base/task.h" // for CancelableTask, etc
12 : #include "base/thread.h" // for Thread
13 : #include "gfxPlatform.h" // for gfxPlatform
14 : #ifdef MOZ_WIDGET_GTK
15 : #include "gfxPlatformGtk.h" // for gfxPlatform
16 : #endif
17 : #include "gfxPrefs.h" // for gfxPrefs
18 : #include "mozilla/AutoRestore.h" // for AutoRestore
19 : #include "mozilla/DebugOnly.h" // for DebugOnly
20 : #include "mozilla/gfx/2D.h" // for DrawTarget
21 : #include "mozilla/gfx/Point.h" // for IntSize
22 : #include "mozilla/gfx/Rect.h" // for IntSize
23 : #include "mozilla/layers/CompositorThread.h"
24 : #include "mozilla/layers/CompositorVsyncSchedulerOwner.h"
25 : #include "mozilla/mozalloc.h" // for operator new, etc
26 : #include "nsCOMPtr.h" // for already_AddRefed
27 : #include "nsDebug.h" // for NS_ASSERTION, etc
28 : #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
29 : #include "nsIWidget.h" // for nsIWidget
30 : #include "nsThreadUtils.h" // for NS_IsMainThread
31 : #include "mozilla/Telemetry.h"
32 : #include "mozilla/VsyncDispatcher.h"
33 : #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
34 : #include "VsyncSource.h"
35 : #endif
36 : #include "mozilla/widget/CompositorWidget.h"
37 : #include "VRManager.h"
38 :
39 : namespace mozilla {
40 :
41 : namespace layers {
42 :
43 : using namespace mozilla::gfx;
44 : using namespace std;
45 :
46 1 : CompositorVsyncScheduler::Observer::Observer(CompositorVsyncScheduler* aOwner)
47 : : mMutex("CompositorVsyncScheduler.Observer.Mutex")
48 1 : , mOwner(aOwner)
49 : {
50 1 : }
51 :
52 0 : CompositorVsyncScheduler::Observer::~Observer()
53 : {
54 0 : MOZ_ASSERT(!mOwner);
55 0 : }
56 :
57 : bool
58 142 : CompositorVsyncScheduler::Observer::NotifyVsync(TimeStamp aVsyncTimestamp)
59 : {
60 284 : MutexAutoLock lock(mMutex);
61 142 : if (!mOwner) {
62 0 : return false;
63 : }
64 142 : return mOwner->NotifyVsync(aVsyncTimestamp);
65 : }
66 :
67 : void
68 0 : CompositorVsyncScheduler::Observer::Destroy()
69 : {
70 0 : MutexAutoLock lock(mMutex);
71 0 : mOwner = nullptr;
72 0 : }
73 :
74 1 : CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorVsyncSchedulerOwner* aVsyncSchedulerOwner,
75 1 : widget::CompositorWidget* aWidget)
76 : : mVsyncSchedulerOwner(aVsyncSchedulerOwner)
77 : , mLastCompose(TimeStamp::Now())
78 : , mIsObservingVsync(false)
79 : , mNeedsComposite(0)
80 : , mVsyncNotificationsSkipped(0)
81 : , mWidget(aWidget)
82 : , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
83 : , mCurrentCompositeTask(nullptr)
84 : , mSetNeedsCompositeMonitor("SetNeedsCompositeMonitor")
85 1 : , mSetNeedsCompositeTask(nullptr)
86 : {
87 1 : mVsyncObserver = new Observer(this);
88 :
89 : // mAsapScheduling is set on the main thread during init,
90 : // but is only accessed after on the compositor thread.
91 2 : mAsapScheduling = gfxPrefs::LayersCompositionFrameRate() == 0 ||
92 1 : gfxPlatform::IsInLayoutAsapMode();
93 1 : }
94 :
95 0 : CompositorVsyncScheduler::~CompositorVsyncScheduler()
96 : {
97 0 : MOZ_ASSERT(!mIsObservingVsync);
98 0 : MOZ_ASSERT(!mVsyncObserver);
99 : // The CompositorVsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners
100 0 : mVsyncSchedulerOwner = nullptr;
101 0 : }
102 :
103 : void
104 0 : CompositorVsyncScheduler::Destroy()
105 : {
106 0 : if (!mVsyncObserver) {
107 : // Destroy was already called on this object.
108 0 : return;
109 : }
110 0 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
111 0 : UnobserveVsync();
112 0 : mVsyncObserver->Destroy();
113 0 : mVsyncObserver = nullptr;
114 :
115 0 : CancelCurrentSetNeedsCompositeTask();
116 0 : CancelCurrentCompositeTask();
117 : }
118 :
119 : void
120 151 : CompositorVsyncScheduler::PostCompositeTask(TimeStamp aCompositeTimestamp)
121 : {
122 : // can be called from the compositor or vsync thread
123 302 : MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
124 151 : if (mCurrentCompositeTask == nullptr && CompositorThreadHolder::Loop()) {
125 302 : RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<TimeStamp>(
126 : "layers::CompositorVsyncScheduler::Composite",
127 : this,
128 : &CompositorVsyncScheduler::Composite,
129 302 : aCompositeTimestamp);
130 151 : mCurrentCompositeTask = task;
131 151 : ScheduleTask(task.forget(), 0);
132 : }
133 151 : }
134 :
135 : void
136 28 : CompositorVsyncScheduler::ScheduleComposition()
137 : {
138 28 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
139 28 : if (!mVsyncObserver) {
140 : // Destroy was already called on this object.
141 0 : return;
142 : }
143 :
144 28 : if (mAsapScheduling) {
145 : // Used only for performance testing purposes
146 0 : PostCompositeTask(TimeStamp::Now());
147 : #ifdef MOZ_WIDGET_ANDROID
148 : } else if (mNeedsComposite >= 2 && mIsObservingVsync) {
149 : // uh-oh, we already requested a composite at least twice so far, and a
150 : // composite hasn't happened yet. It is possible that the vsync observation
151 : // is blocked on the main thread, so let's just composite ASAP and not
152 : // wait for the vsync. Note that this should only ever happen on Fennec
153 : // because there content runs in the same process as the compositor, and so
154 : // content can actually block the main thread in this process.
155 : PostCompositeTask(TimeStamp::Now());
156 : #endif
157 : } else {
158 28 : SetNeedsComposite();
159 : }
160 : }
161 :
162 : void
163 0 : CompositorVsyncScheduler::CancelCurrentSetNeedsCompositeTask()
164 : {
165 0 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
166 0 : MonitorAutoLock lock(mSetNeedsCompositeMonitor);
167 0 : if (mSetNeedsCompositeTask) {
168 0 : mSetNeedsCompositeTask->Cancel();
169 0 : mSetNeedsCompositeTask = nullptr;
170 : }
171 0 : mNeedsComposite = 0;
172 0 : }
173 :
174 : /**
175 : * TODO Potential performance heuristics:
176 : * If a composite takes 17 ms, do we composite ASAP or wait until next vsync?
177 : * If a layer transaction comes after vsync, do we composite ASAP or wait until
178 : * next vsync?
179 : * How many skipped vsync events until we stop listening to vsync events?
180 : */
181 : void
182 28 : CompositorVsyncScheduler::SetNeedsComposite()
183 : {
184 28 : if (!CompositorThreadHolder::IsInCompositorThread()) {
185 0 : MonitorAutoLock lock(mSetNeedsCompositeMonitor);
186 0 : RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod(
187 : "layers::CompositorVsyncScheduler::SetNeedsComposite",
188 : this,
189 0 : &CompositorVsyncScheduler::SetNeedsComposite);
190 0 : mSetNeedsCompositeTask = task;
191 0 : ScheduleTask(task.forget(), 0);
192 0 : return;
193 : } else {
194 56 : MonitorAutoLock lock(mSetNeedsCompositeMonitor);
195 28 : mSetNeedsCompositeTask = nullptr;
196 : }
197 :
198 28 : mNeedsComposite++;
199 28 : if (!mIsObservingVsync && mNeedsComposite) {
200 9 : ObserveVsync();
201 : // Starting to observe vsync is an async operation that goes
202 : // through the main thread of the UI process. It's possible that
203 : // we're blocking there waiting on a composite, so schedule an initial
204 : // one now to get things started.
205 9 : PostCompositeTask(TimeStamp::Now());
206 : }
207 : }
208 :
209 : bool
210 142 : CompositorVsyncScheduler::NotifyVsync(TimeStamp aVsyncTimestamp)
211 : {
212 : // Called from the vsync dispatch thread. When in the GPU Process, that's
213 : // the same as the compositor thread.
214 142 : MOZ_ASSERT_IF(XRE_IsParentProcess(), !CompositorThreadHolder::IsInCompositorThread());
215 142 : MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_GPU, CompositorThreadHolder::IsInCompositorThread());
216 142 : MOZ_ASSERT(!NS_IsMainThread());
217 142 : PostCompositeTask(aVsyncTimestamp);
218 142 : return true;
219 : }
220 :
221 : void
222 1 : CompositorVsyncScheduler::CancelCurrentCompositeTask()
223 : {
224 1 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || NS_IsMainThread());
225 2 : MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
226 1 : if (mCurrentCompositeTask) {
227 0 : mCurrentCompositeTask->Cancel();
228 0 : mCurrentCompositeTask = nullptr;
229 : }
230 1 : }
231 :
232 : void
233 151 : CompositorVsyncScheduler::Composite(TimeStamp aVsyncTimestamp)
234 : {
235 151 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
236 : {
237 302 : MonitorAutoLock lock(mCurrentCompositeTaskMonitor);
238 151 : mCurrentCompositeTask = nullptr;
239 : }
240 :
241 151 : if ((aVsyncTimestamp < mLastCompose) && !mAsapScheduling) {
242 : // We can sometimes get vsync timestamps that are in the past
243 : // compared to the last compose with force composites.
244 : // In those cases, wait until the next vsync;
245 0 : return;
246 : }
247 :
248 151 : MOZ_ASSERT(mVsyncSchedulerOwner);
249 151 : if (!mAsapScheduling && mVsyncSchedulerOwner->IsPendingComposite()) {
250 : // If previous composite is still on going, finish it and does a next
251 : // composite in a next vsync.
252 0 : mVsyncSchedulerOwner->FinishPendingComposite();
253 0 : return;
254 : }
255 :
256 151 : DispatchTouchEvents(aVsyncTimestamp);
257 151 : DispatchVREvents(aVsyncTimestamp);
258 :
259 151 : if (mNeedsComposite || mAsapScheduling) {
260 28 : mNeedsComposite = 0;
261 28 : mLastCompose = aVsyncTimestamp;
262 28 : ComposeToTarget(nullptr);
263 28 : mVsyncNotificationsSkipped = 0;
264 :
265 28 : TimeDuration compositeFrameTotal = TimeStamp::Now() - aVsyncTimestamp;
266 28 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_FRAME_ROUNDTRIP_TIME,
267 56 : compositeFrameTotal.ToMilliseconds());
268 123 : } else if (mVsyncNotificationsSkipped++ > gfxPrefs::CompositorUnobserveCount()) {
269 8 : UnobserveVsync();
270 : }
271 : }
272 :
273 : void
274 1 : CompositorVsyncScheduler::OnForceComposeToTarget()
275 : {
276 : /**
277 : * bug 1138502 - There are cases such as during long-running window resizing events
278 : * where we receive many sync RecvFlushComposites. We also get vsync notifications which
279 : * will increment mVsyncNotificationsSkipped because a composite just occurred. After
280 : * enough vsyncs and RecvFlushComposites occurred, we will disable vsync. Then at the next
281 : * ScheduleComposite, we will enable vsync, then get a RecvFlushComposite, which will
282 : * force us to unobserve vsync again. On some platforms, enabling/disabling vsync is not
283 : * free and this oscillating behavior causes a performance hit. In order to avoid this problem,
284 : * we reset the mVsyncNotificationsSkipped counter to keep vsync enabled.
285 : */
286 1 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
287 1 : mVsyncNotificationsSkipped = 0;
288 1 : }
289 :
290 : void
291 1 : CompositorVsyncScheduler::ForceComposeToTarget(gfx::DrawTarget* aTarget, const IntRect* aRect)
292 : {
293 1 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
294 1 : OnForceComposeToTarget();
295 1 : mLastCompose = TimeStamp::Now();
296 1 : ComposeToTarget(aTarget, aRect);
297 1 : }
298 :
299 : bool
300 1 : CompositorVsyncScheduler::NeedsComposite()
301 : {
302 1 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
303 1 : return mNeedsComposite;
304 : }
305 :
306 : void
307 9 : CompositorVsyncScheduler::ObserveVsync()
308 : {
309 9 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
310 9 : mWidget->ObserveVsync(mVsyncObserver);
311 9 : mIsObservingVsync = true;
312 9 : }
313 :
314 : void
315 8 : CompositorVsyncScheduler::UnobserveVsync()
316 : {
317 8 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
318 8 : mWidget->ObserveVsync(nullptr);
319 8 : mIsObservingVsync = false;
320 8 : }
321 :
322 : void
323 151 : CompositorVsyncScheduler::DispatchTouchEvents(TimeStamp aVsyncTimestamp)
324 : {
325 151 : }
326 :
327 : void
328 151 : CompositorVsyncScheduler::DispatchVREvents(TimeStamp aVsyncTimestamp)
329 : {
330 151 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
331 :
332 151 : VRManager* vm = VRManager::Get();
333 151 : vm->NotifyVsync(aVsyncTimestamp);
334 151 : }
335 :
336 : void
337 151 : CompositorVsyncScheduler::ScheduleTask(already_AddRefed<CancelableRunnable> aTask,
338 : int aTime)
339 : {
340 151 : MOZ_ASSERT(CompositorThreadHolder::Loop());
341 151 : MOZ_ASSERT(aTime >= 0);
342 151 : CompositorThreadHolder::Loop()->PostDelayedTask(Move(aTask), aTime);
343 151 : }
344 :
345 : void
346 0 : CompositorVsyncScheduler::ResumeComposition()
347 : {
348 0 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
349 0 : mLastCompose = TimeStamp::Now();
350 0 : ComposeToTarget(nullptr);
351 0 : }
352 :
353 : void
354 29 : CompositorVsyncScheduler::ComposeToTarget(gfx::DrawTarget* aTarget, const IntRect* aRect)
355 : {
356 29 : MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
357 29 : MOZ_ASSERT(mVsyncSchedulerOwner);
358 29 : mVsyncSchedulerOwner->CompositeToTarget(aTarget, aRect);
359 29 : }
360 :
361 : } // namespace layers
362 : } // namespace mozilla
|