CompositorVsyncScheduler.cpp (14108B)
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 #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 "mozilla/AutoRestore.h" // for AutoRestore 18 #include "mozilla/DebugOnly.h" // for DebugOnly 19 #include "mozilla/StaticPrefs_gfx.h" 20 #include "mozilla/StaticPrefs_layers.h" 21 #include "mozilla/gfx/2D.h" // for DrawTarget 22 #include "mozilla/gfx/Point.h" // for IntSize 23 #include "mozilla/gfx/Rect.h" // for IntSize 24 #include "mozilla/layers/CompositorThread.h" 25 #include "mozilla/layers/CompositorVsyncSchedulerOwner.h" 26 #include "mozilla/mozalloc.h" // for operator new, etc 27 #include "nsCOMPtr.h" // for already_AddRefed 28 #include "nsDebug.h" // for NS_ASSERTION, etc 29 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc 30 #include "nsIWidget.h" // for nsIWidget 31 #include "nsThreadUtils.h" // for NS_IsMainThread 32 #include "mozilla/glean/GfxMetrics.h" 33 #include "mozilla/VsyncDispatcher.h" 34 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) 35 # include "VsyncSource.h" 36 #endif 37 #include "mozilla/widget/CompositorWidget.h" 38 #include "VRManager.h" 39 40 namespace mozilla { 41 42 namespace layers { 43 44 using namespace mozilla::gfx; 45 46 CompositorVsyncScheduler::Observer::Observer(CompositorVsyncScheduler* aOwner) 47 : mMutex("CompositorVsyncScheduler.Observer.Mutex"), mOwner(aOwner) {} 48 49 CompositorVsyncScheduler::Observer::~Observer() { MOZ_ASSERT(!mOwner); } 50 51 void CompositorVsyncScheduler::Observer::NotifyVsync(const VsyncEvent& aVsync) { 52 MutexAutoLock lock(mMutex); 53 if (!mOwner) { 54 return; 55 } 56 mOwner->NotifyVsync(aVsync); 57 } 58 59 void CompositorVsyncScheduler::Observer::Destroy() { 60 MutexAutoLock lock(mMutex); 61 mOwner = nullptr; 62 } 63 64 CompositorVsyncScheduler::CompositorVsyncScheduler( 65 CompositorVsyncSchedulerOwner* aVsyncSchedulerOwner, 66 widget::CompositorWidget* aWidget) 67 : mVsyncSchedulerOwner(aVsyncSchedulerOwner), 68 mLastComposeTime(SampleTime::FromNow()), 69 mLastVsyncTime(TimeStamp::Now()), 70 mLastVsyncOutputTime(TimeStamp::Now()), 71 mIsObservingVsync(false), 72 mRendersDelayedByVsyncReasons(wr::RenderReasons::NONE), 73 mVsyncNotificationsSkipped(0), 74 mWidget(aWidget), 75 mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor"), 76 mCurrentCompositeTask(nullptr), 77 mCurrentCompositeTaskReasons(wr::RenderReasons::NONE), 78 mCurrentVRTaskMonitor("CurrentVRTaskMonitor"), 79 mCurrentVRTask(nullptr) { 80 mVsyncObserver = new Observer(this); 81 82 // mAsapScheduling is set on the main thread during init, 83 // but is only accessed after on the compositor thread. 84 mAsapScheduling = 85 StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 || 86 gfxPlatform::IsInLayoutAsapMode(); 87 } 88 89 CompositorVsyncScheduler::~CompositorVsyncScheduler() { 90 MOZ_ASSERT(!mIsObservingVsync); 91 MOZ_ASSERT(!mVsyncObserver); 92 // The CompositorVsyncDispatcher is cleaned up before this in the 93 // nsIWidget, which stops vsync listeners 94 mVsyncSchedulerOwner = nullptr; 95 } 96 97 void CompositorVsyncScheduler::Destroy() { 98 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 99 100 if (!mVsyncObserver) { 101 // Destroy was already called on this object. 102 return; 103 } 104 UnobserveVsync(); 105 mVsyncObserver->Destroy(); 106 mVsyncObserver = nullptr; 107 108 mCompositeRequestedAt = TimeStamp(); 109 CancelCurrentCompositeTask(); 110 CancelCurrentVRTask(); 111 } 112 113 void CompositorVsyncScheduler::PostCompositeTask(const VsyncEvent& aVsyncEvent, 114 wr::RenderReasons aReasons) { 115 MonitorAutoLock lock(mCurrentCompositeTaskMonitor); 116 mCurrentCompositeTaskReasons = mCurrentCompositeTaskReasons | aReasons; 117 if (mCurrentCompositeTask == nullptr && CompositorThread()) { 118 RefPtr<CancelableRunnable> task = 119 NewCancelableRunnableMethod<VsyncEvent, wr::RenderReasons>( 120 "layers::CompositorVsyncScheduler::Composite", this, 121 &CompositorVsyncScheduler::Composite, aVsyncEvent, aReasons); 122 mCurrentCompositeTask = task; 123 CompositorThread()->Dispatch(task.forget()); 124 } 125 } 126 127 void CompositorVsyncScheduler::PostVRTask(TimeStamp aTimestamp) { 128 MonitorAutoLock lockVR(mCurrentVRTaskMonitor); 129 if (mCurrentVRTask == nullptr && CompositorThread()) { 130 RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<TimeStamp>( 131 "layers::CompositorVsyncScheduler::DispatchVREvents", this, 132 &CompositorVsyncScheduler::DispatchVREvents, aTimestamp); 133 mCurrentVRTask = task; 134 CompositorThread()->Dispatch(task.forget()); 135 } 136 } 137 138 void CompositorVsyncScheduler::ScheduleComposition(wr::RenderReasons aReasons) { 139 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 140 if (!mVsyncObserver) { 141 // Destroy was already called on this object. 142 return; 143 } 144 145 // Make a synthetic vsync event for the calls to PostCompositeTask below. 146 TimeStamp vsyncTime = TimeStamp::Now(); 147 TimeStamp outputTime = vsyncTime + mVsyncSchedulerOwner->GetVsyncInterval(); 148 VsyncEvent vsyncEvent(VsyncId(), vsyncTime, outputTime); 149 150 if (mAsapScheduling) { 151 // Used only for performance testing purposes, and when recording/replaying 152 // to ensure that graphics are up to date. 153 PostCompositeTask(vsyncEvent, aReasons); 154 } else { 155 if (!mCompositeRequestedAt) { 156 mCompositeRequestedAt = TimeStamp::Now(); 157 } 158 if (!mIsObservingVsync && mCompositeRequestedAt) { 159 ObserveVsync(); 160 // Starting to observe vsync is an async operation that goes 161 // through the main thread of the UI process. It's possible that 162 // we're blocking there waiting on a composite, so schedule an initial 163 // one now to get things started. 164 PostCompositeTask(vsyncEvent, 165 aReasons | wr::RenderReasons::START_OBSERVING_VSYNC); 166 } else { 167 mRendersDelayedByVsyncReasons = aReasons; 168 } 169 } 170 } 171 172 void CompositorVsyncScheduler::NotifyVsync(const VsyncEvent& aVsync) { 173 // Called from the vsync dispatch thread. When in the GPU Process, that's 174 // the same as the compositor thread. 175 #ifdef DEBUG 176 # ifdef MOZ_WAYLAND 177 // On Wayland, we dispatch vsync from the main thread, without a GPU process. 178 // To allow this, we skip the following asserts if we're currently utilizing 179 // the Wayland backend. The IsParentProcess guard is needed to ensure that 180 // we don't accidentally attempt to initialize the gfxPlatform in the GPU 181 // process on X11. 182 if (!XRE_IsParentProcess() || 183 !gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) 184 # endif // MOZ_WAYLAND 185 { 186 MOZ_ASSERT_IF(XRE_IsParentProcess(), 187 !CompositorThreadHolder::IsInCompositorThread()); 188 MOZ_ASSERT(!NS_IsMainThread()); 189 } 190 191 MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_GPU, 192 CompositorThreadHolder::IsInCompositorThread()); 193 #endif // DEBUG 194 195 #if defined(MOZ_WIDGET_ANDROID) 196 gfx::VRManager* vm = gfx::VRManager::Get(); 197 if (!vm->IsPresenting()) { 198 PostCompositeTask(aVsync, wr::RenderReasons::VSYNC); 199 } 200 #else 201 PostCompositeTask(aVsync, wr::RenderReasons::VSYNC); 202 #endif // defined(MOZ_WIDGET_ANDROID) 203 204 PostVRTask(aVsync.mTime); 205 } 206 207 void CompositorVsyncScheduler::CancelCurrentVRTask() { 208 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || 209 NS_IsMainThread()); 210 MonitorAutoLock lock(mCurrentVRTaskMonitor); 211 if (mCurrentVRTask) { 212 mCurrentVRTask->Cancel(); 213 mCurrentVRTask = nullptr; 214 } 215 } 216 217 wr::RenderReasons CompositorVsyncScheduler::CancelCurrentCompositeTask() { 218 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || 219 NS_IsMainThread()); 220 MonitorAutoLock lock(mCurrentCompositeTaskMonitor); 221 wr::RenderReasons canceledTaskRenderReasons = mCurrentCompositeTaskReasons; 222 mCurrentCompositeTaskReasons = wr::RenderReasons::NONE; 223 if (mCurrentCompositeTask) { 224 mCurrentCompositeTask->Cancel(); 225 mCurrentCompositeTask = nullptr; 226 } 227 228 return canceledTaskRenderReasons; 229 } 230 231 void CompositorVsyncScheduler::Composite(const VsyncEvent& aVsyncEvent, 232 wr::RenderReasons aReasons) { 233 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 234 MOZ_ASSERT(mVsyncSchedulerOwner); 235 236 { // scope lock 237 MonitorAutoLock lock(mCurrentCompositeTaskMonitor); 238 aReasons = 239 aReasons | mCurrentCompositeTaskReasons | mRendersDelayedByVsyncReasons; 240 mCurrentCompositeTaskReasons = wr::RenderReasons::NONE; 241 mRendersDelayedByVsyncReasons = wr::RenderReasons::NONE; 242 mCurrentCompositeTask = nullptr; 243 } 244 245 mLastVsyncTime = aVsyncEvent.mTime; 246 mLastVsyncOutputTime = aVsyncEvent.mOutputTime; 247 mLastVsyncId = aVsyncEvent.mId; 248 249 if (!mAsapScheduling) { 250 // Some early exit conditions if we're not in ASAP mode 251 if (aVsyncEvent.mTime < mLastComposeTime.Time()) { 252 // We can sometimes get vsync timestamps that are in the past 253 // compared to the last compose with force composites. 254 // In those cases, wait until the next vsync; 255 return; 256 } 257 258 if (mVsyncSchedulerOwner->IsPendingComposite()) { 259 // If previous composite is still on going, finish it and wait for the 260 // next vsync. 261 mVsyncSchedulerOwner->FinishPendingComposite(); 262 return; 263 } 264 } 265 266 if (mCompositeRequestedAt || mAsapScheduling) { 267 mCompositeRequestedAt = TimeStamp(); 268 mLastComposeTime = SampleTime::FromVsync(aVsyncEvent.mTime); 269 270 // Tell the owner to do a composite 271 mVsyncSchedulerOwner->CompositeToTarget(aVsyncEvent.mId, aReasons, nullptr, 272 nullptr); 273 274 mVsyncNotificationsSkipped = 0; 275 276 TimeDuration compositeFrameTotal = TimeStamp::Now() - aVsyncEvent.mTime; 277 mozilla::glean::gfx::composite_frame_roundtrip_time.AccumulateRawDuration( 278 compositeFrameTotal); 279 } else if (mVsyncNotificationsSkipped++ > 280 StaticPrefs::gfx_vsync_compositor_unobserve_count_AtStartup()) { 281 UnobserveVsync(); 282 } 283 } 284 285 void CompositorVsyncScheduler::ForceComposeToTarget(wr::RenderReasons aReasons, 286 gfx::DrawTarget* aTarget, 287 const IntRect* aRect) { 288 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 289 290 /** 291 * bug 1138502 - There are cases such as during long-running window resizing 292 * events where we receive many force-composites. We also continue to get 293 * vsync notifications. Because the force-composites trigger compositing and 294 * clear the mCompositeRequestedAt timestamp, the vsync notifications will not 295 * need to do anything and so will increment the mVsyncNotificationsSkipped 296 * counter to indicate the vsync was ignored. If this happens enough times, we 297 * will disable listening for vsync entirely. On the next force-composite we 298 * will enable listening for vsync again, and continued force-composites and 299 * vsyncs will cause oscillation between observing vsync and not. On some 300 * platforms, enabling/disabling vsync is not free and this oscillating 301 * behavior causes a performance hit. In order to avoid this problem, we reset 302 * the mVsyncNotificationsSkipped counter to keep vsync enabled. 303 */ 304 mVsyncNotificationsSkipped = 0; 305 306 mLastComposeTime = SampleTime::FromNow(); 307 MOZ_ASSERT(mVsyncSchedulerOwner); 308 mVsyncSchedulerOwner->CompositeToTarget(VsyncId(), aReasons, aTarget, aRect); 309 } 310 311 bool CompositorVsyncScheduler::NeedsComposite() { 312 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 313 return (bool)mCompositeRequestedAt; 314 } 315 316 bool CompositorVsyncScheduler::FlushPendingComposite() { 317 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 318 if (mCompositeRequestedAt) { 319 wr::RenderReasons reasons = CancelCurrentCompositeTask(); 320 ForceComposeToTarget(reasons, nullptr, nullptr); 321 return true; 322 } 323 return false; 324 } 325 326 void CompositorVsyncScheduler::ObserveVsync() { 327 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 328 mWidget->ObserveVsync(mVsyncObserver); 329 mIsObservingVsync = true; 330 } 331 332 void CompositorVsyncScheduler::UnobserveVsync() { 333 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 334 mWidget->ObserveVsync(nullptr); 335 mIsObservingVsync = false; 336 } 337 338 void CompositorVsyncScheduler::DispatchVREvents(TimeStamp aVsyncTimestamp) { 339 { 340 MonitorAutoLock lock(mCurrentVRTaskMonitor); 341 mCurrentVRTask = nullptr; 342 } 343 // This only allows to be called by CompositorVsyncScheduler::PostVRTask() 344 // When the process is going to shutdown, the runnable has chance to be 345 // executed by other threads, we only want it to be run in the compositor 346 // thread. 347 if (!CompositorThreadHolder::IsInCompositorThread()) { 348 return; 349 } 350 351 VRManager* vm = VRManager::Get(); 352 vm->NotifyVsync(aVsyncTimestamp); 353 } 354 355 const SampleTime& CompositorVsyncScheduler::GetLastComposeTime() const { 356 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 357 return mLastComposeTime; 358 } 359 360 const TimeStamp& CompositorVsyncScheduler::GetLastVsyncTime() const { 361 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 362 return mLastVsyncTime; 363 } 364 365 const TimeStamp& CompositorVsyncScheduler::GetLastVsyncOutputTime() const { 366 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 367 return mLastVsyncOutputTime; 368 } 369 370 const VsyncId& CompositorVsyncScheduler::GetLastVsyncId() const { 371 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 372 return mLastVsyncId; 373 } 374 375 void CompositorVsyncScheduler::UpdateLastComposeTime() { 376 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 377 mLastComposeTime = SampleTime::FromNow(); 378 } 379 380 } // namespace layers 381 } // namespace mozilla