/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=2 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/layers/CompositorVsyncScheduler.h" #include // for fprintf, stdout #include // for uint64_t #include "base/task.h" // for CancelableTask, etc #include "base/thread.h" // for Thread #include "gfxPlatform.h" // for gfxPlatform #ifdef MOZ_WIDGET_GTK #include "gfxPlatformGtk.h" // for gfxPlatform #endif #include "gfxPrefs.h" // for gfxPrefs #include "mozilla/AutoRestore.h" // for AutoRestore #include "mozilla/DebugOnly.h" // for DebugOnly #include "mozilla/gfx/2D.h" // for DrawTarget #include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/gfx/Rect.h" // for IntSize #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/CompositorVsyncSchedulerOwner.h" #include "mozilla/mozalloc.h" // for operator new, etc #include "nsCOMPtr.h" // for already_AddRefed #include "nsDebug.h" // for NS_ASSERTION, etc #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc #include "nsIWidget.h" // for nsIWidget #include "nsThreadUtils.h" // for NS_IsMainThread #include "mozilla/Telemetry.h" #include "mozilla/VsyncDispatcher.h" #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) #include "VsyncSource.h" #endif #include "mozilla/widget/CompositorWidget.h" #include "VRManager.h" namespace mozilla { namespace layers { using namespace mozilla::gfx; using namespace std; CompositorVsyncScheduler::Observer::Observer(CompositorVsyncScheduler* aOwner) : mMutex("CompositorVsyncScheduler.Observer.Mutex") , mOwner(aOwner) { } CompositorVsyncScheduler::Observer::~Observer() { MOZ_ASSERT(!mOwner); } bool CompositorVsyncScheduler::Observer::NotifyVsync(TimeStamp aVsyncTimestamp) { MutexAutoLock lock(mMutex); if (!mOwner) { return false; } return mOwner->NotifyVsync(aVsyncTimestamp); } void CompositorVsyncScheduler::Observer::Destroy() { MutexAutoLock lock(mMutex); mOwner = nullptr; } CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorVsyncSchedulerOwner* aVsyncSchedulerOwner, widget::CompositorWidget* aWidget) : mVsyncSchedulerOwner(aVsyncSchedulerOwner) , mLastCompose(TimeStamp::Now()) , mIsObservingVsync(false) , mNeedsComposite(0) , mVsyncNotificationsSkipped(0) , mWidget(aWidget) , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor") , mCurrentCompositeTask(nullptr) , mSetNeedsCompositeMonitor("SetNeedsCompositeMonitor") , mSetNeedsCompositeTask(nullptr) { mVsyncObserver = new Observer(this); // mAsapScheduling is set on the main thread during init, // but is only accessed after on the compositor thread. mAsapScheduling = gfxPrefs::LayersCompositionFrameRate() == 0 || gfxPlatform::IsInLayoutAsapMode(); } CompositorVsyncScheduler::~CompositorVsyncScheduler() { MOZ_ASSERT(!mIsObservingVsync); MOZ_ASSERT(!mVsyncObserver); // The CompositorVsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners mVsyncSchedulerOwner = nullptr; } void CompositorVsyncScheduler::Destroy() { if (!mVsyncObserver) { // Destroy was already called on this object. return; } MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); UnobserveVsync(); mVsyncObserver->Destroy(); mVsyncObserver = nullptr; CancelCurrentSetNeedsCompositeTask(); CancelCurrentCompositeTask(); } void CompositorVsyncScheduler::PostCompositeTask(TimeStamp aCompositeTimestamp) { // can be called from the compositor or vsync thread MonitorAutoLock lock(mCurrentCompositeTaskMonitor); if (mCurrentCompositeTask == nullptr && CompositorThreadHolder::Loop()) { RefPtr task = NewCancelableRunnableMethod(this, &CompositorVsyncScheduler::Composite, aCompositeTimestamp); mCurrentCompositeTask = task; ScheduleTask(task.forget(), 0); } } void CompositorVsyncScheduler::ScheduleComposition() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); if (!mVsyncObserver) { // Destroy was already called on this object. return; } if (mAsapScheduling) { // Used only for performance testing purposes PostCompositeTask(TimeStamp::Now()); #ifdef MOZ_WIDGET_ANDROID } else if (mNeedsComposite >= 2 && mIsObservingVsync) { // uh-oh, we already requested a composite at least twice so far, and a // composite hasn't happened yet. It is possible that the vsync observation // is blocked on the main thread, so let's just composite ASAP and not // wait for the vsync. Note that this should only ever happen on Fennec // because there content runs in the same process as the compositor, and so // content can actually block the main thread in this process. PostCompositeTask(TimeStamp::Now()); #endif } else { SetNeedsComposite(); } } void CompositorVsyncScheduler::CancelCurrentSetNeedsCompositeTask() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MonitorAutoLock lock(mSetNeedsCompositeMonitor); if (mSetNeedsCompositeTask) { mSetNeedsCompositeTask->Cancel(); mSetNeedsCompositeTask = nullptr; } mNeedsComposite = 0; } /** * TODO Potential performance heuristics: * If a composite takes 17 ms, do we composite ASAP or wait until next vsync? * If a layer transaction comes after vsync, do we composite ASAP or wait until * next vsync? * How many skipped vsync events until we stop listening to vsync events? */ void CompositorVsyncScheduler::SetNeedsComposite() { if (!CompositorThreadHolder::IsInCompositorThread()) { MonitorAutoLock lock(mSetNeedsCompositeMonitor); RefPtr task = NewCancelableRunnableMethod(this, &CompositorVsyncScheduler::SetNeedsComposite); mSetNeedsCompositeTask = task; ScheduleTask(task.forget(), 0); return; } else { MonitorAutoLock lock(mSetNeedsCompositeMonitor); mSetNeedsCompositeTask = nullptr; } mNeedsComposite++; if (!mIsObservingVsync && mNeedsComposite) { ObserveVsync(); // Starting to observe vsync is an async operation that goes // through the main thread of the UI process. It's possible that // we're blocking there waiting on a composite, so schedule an initial // one now to get things started. PostCompositeTask(TimeStamp::Now()); } } bool CompositorVsyncScheduler::NotifyVsync(TimeStamp aVsyncTimestamp) { // Called from the vsync dispatch thread. When in the GPU Process, that's // the same as the compositor thread. MOZ_ASSERT_IF(XRE_IsParentProcess(), !CompositorThreadHolder::IsInCompositorThread()); MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_GPU, CompositorThreadHolder::IsInCompositorThread()); MOZ_ASSERT(!NS_IsMainThread()); PostCompositeTask(aVsyncTimestamp); return true; } void CompositorVsyncScheduler::CancelCurrentCompositeTask() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || NS_IsMainThread()); MonitorAutoLock lock(mCurrentCompositeTaskMonitor); if (mCurrentCompositeTask) { mCurrentCompositeTask->Cancel(); mCurrentCompositeTask = nullptr; } } void CompositorVsyncScheduler::Composite(TimeStamp aVsyncTimestamp) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); { MonitorAutoLock lock(mCurrentCompositeTaskMonitor); mCurrentCompositeTask = nullptr; } if ((aVsyncTimestamp < mLastCompose) && !mAsapScheduling) { // We can sometimes get vsync timestamps that are in the past // compared to the last compose with force composites. // In those cases, wait until the next vsync; return; } MOZ_ASSERT(mVsyncSchedulerOwner); if (!mAsapScheduling && mVsyncSchedulerOwner->IsPendingComposite()) { // If previous composite is still on going, finish it and does a next // composite in a next vsync. mVsyncSchedulerOwner->FinishPendingComposite(); return; } DispatchTouchEvents(aVsyncTimestamp); DispatchVREvents(aVsyncTimestamp); if (mNeedsComposite || mAsapScheduling) { mNeedsComposite = 0; mLastCompose = aVsyncTimestamp; ComposeToTarget(nullptr); mVsyncNotificationsSkipped = 0; TimeDuration compositeFrameTotal = TimeStamp::Now() - aVsyncTimestamp; mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_FRAME_ROUNDTRIP_TIME, compositeFrameTotal.ToMilliseconds()); } else if (mVsyncNotificationsSkipped++ > gfxPrefs::CompositorUnobserveCount()) { UnobserveVsync(); } } void CompositorVsyncScheduler::OnForceComposeToTarget() { /** * bug 1138502 - There are cases such as during long-running window resizing events * where we receive many sync RecvFlushComposites. We also get vsync notifications which * will increment mVsyncNotificationsSkipped because a composite just occurred. After * enough vsyncs and RecvFlushComposites occurred, we will disable vsync. Then at the next * ScheduleComposite, we will enable vsync, then get a RecvFlushComposite, which will * force us to unobserve vsync again. On some platforms, enabling/disabling vsync is not * free and this oscillating behavior causes a performance hit. In order to avoid this problem, * we reset the mVsyncNotificationsSkipped counter to keep vsync enabled. */ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); mVsyncNotificationsSkipped = 0; } void CompositorVsyncScheduler::ForceComposeToTarget(gfx::DrawTarget* aTarget, const IntRect* aRect) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); OnForceComposeToTarget(); mLastCompose = TimeStamp::Now(); ComposeToTarget(aTarget, aRect); } bool CompositorVsyncScheduler::NeedsComposite() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); return mNeedsComposite; } void CompositorVsyncScheduler::ObserveVsync() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); mWidget->ObserveVsync(mVsyncObserver); mIsObservingVsync = true; } void CompositorVsyncScheduler::UnobserveVsync() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); mWidget->ObserveVsync(nullptr); mIsObservingVsync = false; } void CompositorVsyncScheduler::DispatchTouchEvents(TimeStamp aVsyncTimestamp) { } void CompositorVsyncScheduler::DispatchVREvents(TimeStamp aVsyncTimestamp) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); VRManager* vm = VRManager::Get(); vm->NotifyVsync(aVsyncTimestamp); } void CompositorVsyncScheduler::ScheduleTask(already_AddRefed aTask, int aTime) { MOZ_ASSERT(CompositorThreadHolder::Loop()); MOZ_ASSERT(aTime >= 0); CompositorThreadHolder::Loop()->PostDelayedTask(Move(aTask), aTime); } void CompositorVsyncScheduler::ResumeComposition() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); mLastCompose = TimeStamp::Now(); ComposeToTarget(nullptr); } void CompositorVsyncScheduler::ComposeToTarget(gfx::DrawTarget* aTarget, const IntRect* aRect) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MOZ_ASSERT(mVsyncSchedulerOwner); mVsyncSchedulerOwner->CompositeToTarget(aTarget, aRect); } } // namespace layers } // namespace mozilla