/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 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" #include "VRThread.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) , mCurrentVRListenerTaskMonitor("CurrentVRTaskMonitor") , mCurrentVRListenerTask(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() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); if (!mVsyncObserver) { // Destroy was already called on this object. return; } UnobserveVsync(); mVsyncObserver->Destroy(); mVsyncObserver = nullptr; mNeedsComposite = 0; CancelCurrentCompositeTask(); } void CompositorVsyncScheduler::PostCompositeTask(TimeStamp aCompositeTimestamp) { MonitorAutoLock lock(mCurrentCompositeTaskMonitor); if (mCurrentCompositeTask == nullptr && CompositorThreadHolder::Loop()) { RefPtr task = NewCancelableRunnableMethod( "layers::CompositorVsyncScheduler::Composite", this, &CompositorVsyncScheduler::Composite, aCompositeTimestamp); mCurrentCompositeTask = task; ScheduleTask(task.forget()); } } void CompositorVsyncScheduler::PostVRTask(TimeStamp aTimestamp) { MonitorAutoLock lockVR(mCurrentVRListenerTaskMonitor); if (mCurrentVRListenerTask == nullptr && VRListenerThreadHolder::Loop()) { RefPtr task = NewRunnableMethod( "layers::CompositorVsyncScheduler::DispatchVREvents", this, &CompositorVsyncScheduler::DispatchVREvents, aTimestamp); mCurrentVRListenerTask = task; VRListenerThreadHolder::Loop()->PostDelayedTask(Move(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 { 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); PostVRTask(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()); MOZ_ASSERT(mVsyncSchedulerOwner); { // scope lock MonitorAutoLock lock(mCurrentCompositeTaskMonitor); mCurrentCompositeTask = nullptr; } if (!mAsapScheduling) { // Some early exit conditions if we're not in ASAP mode if (aVsyncTimestamp < mLastCompose) { // 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; } if (mVsyncSchedulerOwner->IsPendingComposite()) { // If previous composite is still on going, finish it and wait for the // next vsync. mVsyncSchedulerOwner->FinishPendingComposite(); return; } } if (mNeedsComposite || mAsapScheduling) { mNeedsComposite = 0; mLastCompose = aVsyncTimestamp; // Tell the owner to do a composite mVsyncSchedulerOwner->CompositeToTarget(nullptr, 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::ForceComposeToTarget(gfx::DrawTarget* aTarget, const IntRect* aRect) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); /** * bug 1138502 - There are cases such as during long-running window resizing * events where we receive many force-composites. We also continue to get * vsync notifications. Because the force-composites trigger compositing and * clear the mNeedsComposite counter, the vsync notifications will not need * to do anything and so will increment the mVsyncNotificationsSkipped counter * to indicate the vsync was ignored. If this happens enough times, we will * disable listening for vsync entirely. On the next force-composite we will * enable listening for vsync again, and continued force-composites and vsyncs * will cause oscillation between observing vsync and not. * 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. */ mVsyncNotificationsSkipped = 0; mLastCompose = TimeStamp::Now(); MOZ_ASSERT(mVsyncSchedulerOwner); mVsyncSchedulerOwner->CompositeToTarget(aTarget, aRect); } bool CompositorVsyncScheduler::NeedsComposite() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); return mNeedsComposite; } bool CompositorVsyncScheduler::FlushPendingComposite() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); if (mNeedsComposite) { CancelCurrentCompositeTask(); ForceComposeToTarget(nullptr, nullptr); return true; } return false; } 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::DispatchVREvents(TimeStamp aVsyncTimestamp) { { MonitorAutoLock lock(mCurrentVRListenerTaskMonitor); mCurrentVRListenerTask = nullptr; } // This only allows to be called by CompositorVsyncScheduler::PostVRTask() // When the process is going to shutdown, the runnable has chance to be executed // by other threads, we only want it to be run at VRListenerThread. if (!VRListenerThreadHolder::IsInVRListenerThread()) { return; } VRManager* vm = VRManager::Get(); vm->NotifyVsync(aVsyncTimestamp); } void CompositorVsyncScheduler::ScheduleTask(already_AddRefed aTask) { MOZ_ASSERT(CompositorThreadHolder::Loop()); CompositorThreadHolder::Loop()->PostDelayedTask(Move(aTask), 0); } const TimeStamp& CompositorVsyncScheduler::GetLastComposeTime() const { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); return mLastCompose; } } // namespace layers } // namespace mozilla