diff options
Diffstat (limited to 'widget/VsyncDispatcher.cpp')
-rw-r--r-- | widget/VsyncDispatcher.cpp | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/widget/VsyncDispatcher.cpp b/widget/VsyncDispatcher.cpp new file mode 100644 index 0000000000..acb00a368a --- /dev/null +++ b/widget/VsyncDispatcher.cpp @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "MainThreadUtils.h" +#include "VsyncDispatcher.h" +#include "VsyncSource.h" +#include "gfxPlatform.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/StaticPrefs_gfx.h" + +using namespace mozilla::layers; + +namespace mozilla { + +CompositorVsyncDispatcher::CompositorVsyncDispatcher( + RefPtr<VsyncDispatcher> aVsyncDispatcher) + : mVsyncDispatcher(std::move(aVsyncDispatcher)), + mCompositorObserverLock("CompositorObserverLock"), + mDidShutdown(false) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); +} + +CompositorVsyncDispatcher::~CompositorVsyncDispatcher() { + MOZ_ASSERT(XRE_IsParentProcess()); +} + +void CompositorVsyncDispatcher::NotifyVsync(const VsyncEvent& aVsync) { + // In vsync thread + layers::CompositorBridgeParent::PostInsertVsyncProfilerMarker(aVsync.mTime); + + MutexAutoLock lock(mCompositorObserverLock); + if (mCompositorVsyncObserver) { + mCompositorVsyncObserver->NotifyVsync(aVsync); + } +} + +void CompositorVsyncDispatcher::ObserveVsync(bool aEnable) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + if (mDidShutdown) { + return; + } + + if (aEnable) { + mVsyncDispatcher->AddVsyncObserver(this); + } else { + mVsyncDispatcher->RemoveVsyncObserver(this); + } +} + +void CompositorVsyncDispatcher::SetCompositorVsyncObserver( + VsyncObserver* aVsyncObserver) { + // When remote compositing or running gtests, vsync observation is + // initiated on the main thread. Otherwise, it is initiated from the + // compositor thread. + MOZ_ASSERT(NS_IsMainThread() || + CompositorThreadHolder::IsInCompositorThread()); + + { // scope lock + MutexAutoLock lock(mCompositorObserverLock); + mCompositorVsyncObserver = aVsyncObserver; + } + + bool observeVsync = aVsyncObserver != nullptr; + nsCOMPtr<nsIRunnable> vsyncControl = NewRunnableMethod<bool>( + "CompositorVsyncDispatcher::ObserveVsync", this, + &CompositorVsyncDispatcher::ObserveVsync, observeVsync); + NS_DispatchToMainThread(vsyncControl); +} + +void CompositorVsyncDispatcher::Shutdown() { + // Need to explicitly remove CompositorVsyncDispatcher when the nsBaseWidget + // shuts down. Otherwise, we would get dead vsync notifications between when + // the nsBaseWidget shuts down and the CompositorBridgeParent shuts down. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDidShutdown); + ObserveVsync(false); + mDidShutdown = true; + { // scope lock + MutexAutoLock lock(mCompositorObserverLock); + mCompositorVsyncObserver = nullptr; + } + mVsyncDispatcher = nullptr; +} + +VsyncDispatcher::VsyncDispatcher(gfx::VsyncSource* aVsyncSource) + : mState(State(aVsyncSource), "VsyncDispatcher::mState") { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); +} + +VsyncDispatcher::~VsyncDispatcher() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); +} + +void VsyncDispatcher::SetVsyncSource(gfx::VsyncSource* aVsyncSource) { + MOZ_RELEASE_ASSERT(aVsyncSource); + + auto state = mState.Lock(); + if (aVsyncSource == state->mCurrentVsyncSource) { + return; + } + + if (state->mIsObservingVsync) { + state->mCurrentVsyncSource->RemoveVsyncDispatcher(this); + aVsyncSource->AddVsyncDispatcher(this); + } + state->mCurrentVsyncSource = aVsyncSource; +} + +RefPtr<gfx::VsyncSource> VsyncDispatcher::GetCurrentVsyncSource() { + auto state = mState.Lock(); + return state->mCurrentVsyncSource; +} + +TimeDuration VsyncDispatcher::GetVsyncRate() { + auto state = mState.Lock(); + return state->mCurrentVsyncSource->GetVsyncRate(); +} + +static int32_t ComputeFrameRateDivisor(gfx::VsyncSource* aCurrentVsyncSource) { + int32_t maxRate = StaticPrefs::gfx_display_max_frame_rate(); + if (maxRate == 0) { + return StaticPrefs::gfx_display_frame_rate_divisor(); + } + + // Compute the frame rate divisor based on max frame rates. + double frameDuration = aCurrentVsyncSource->GetVsyncRate().ToMilliseconds(); + + // Respect the pref gfx.display.frame-rate-divisor if larger. + return std::max(StaticPrefs::gfx_display_frame_rate_divisor(), + int32_t(floor(1000.0 / frameDuration / maxRate))); +} + +void VsyncDispatcher::NotifyVsync(const VsyncEvent& aVsync) { + nsTArray<RefPtr<VsyncObserver>> observers; + bool shouldDispatchToMainThread = false; + { + auto state = mState.Lock(); + if (++state->mVsyncSkipCounter < + ComputeFrameRateDivisor(state->mCurrentVsyncSource)) { + return; + } + state->mVsyncSkipCounter = 0; + + // Copy out the observers so that we don't keep the mutex + // locked while notifying vsync. + observers = state->mObservers.Clone(); + shouldDispatchToMainThread = !state->mMainThreadObservers.IsEmpty() && + (state->mLastVsyncIdSentToMainThread == + state->mLastMainThreadProcessedVsyncId); + } + + for (const auto& observer : observers) { + observer->NotifyVsync(aVsync); + } + + if (shouldDispatchToMainThread) { + auto state = mState.Lock(); + state->mLastVsyncIdSentToMainThread = aVsync.mId; + NS_DispatchToMainThread(NewRunnableMethod<VsyncEvent>( + "VsyncDispatcher::NotifyMainThreadObservers", this, + &VsyncDispatcher::NotifyMainThreadObservers, aVsync)); + } +} + +void VsyncDispatcher::NotifyMainThreadObservers(VsyncEvent aEvent) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray<RefPtr<VsyncObserver>> observers; + { + // Copy out the main thread observers so that we don't keep the mutex + // locked while notifying vsync. + auto state = mState.Lock(); + observers.AppendElements(state->mMainThreadObservers); + } + + for (const auto& observer : observers) { + observer->NotifyVsync(aEvent); + } + + { // Scope lock + auto state = mState.Lock(); + state->mLastMainThreadProcessedVsyncId = aEvent.mId; + } +} + +void VsyncDispatcher::AddVsyncObserver(VsyncObserver* aVsyncObserver) { + MOZ_ASSERT(aVsyncObserver); + { // scope lock - called on PBackground thread or main thread + auto state = mState.Lock(); + if (!state->mObservers.Contains(aVsyncObserver)) { + state->mObservers.AppendElement(aVsyncObserver); + } + } + + UpdateVsyncStatus(); +} + +void VsyncDispatcher::RemoveVsyncObserver(VsyncObserver* aVsyncObserver) { + MOZ_ASSERT(aVsyncObserver); + { // scope lock - called on PBackground thread or main thread + auto state = mState.Lock(); + state->mObservers.RemoveElement(aVsyncObserver); + } + + UpdateVsyncStatus(); +} + +void VsyncDispatcher::AddMainThreadObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aObserver); + { + auto state = mState.Lock(); + state->mMainThreadObservers.AppendElement(aObserver); + } + + UpdateVsyncStatus(); +} + +void VsyncDispatcher::RemoveMainThreadObserver(VsyncObserver* aObserver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aObserver); + { + auto state = mState.Lock(); + state->mMainThreadObservers.RemoveElement(aObserver); + } + + UpdateVsyncStatus(); +} + +void VsyncDispatcher::UpdateVsyncStatus() { + bool wasObservingVsync = false; + bool needVsync = false; + RefPtr<gfx::VsyncSource> vsyncSource; + + { + auto state = mState.Lock(); + wasObservingVsync = state->mIsObservingVsync; + needVsync = + !state->mObservers.IsEmpty() || !state->mMainThreadObservers.IsEmpty(); + state->mIsObservingVsync = needVsync; + vsyncSource = state->mCurrentVsyncSource; + } + + // Call Add/RemoveVsyncDispatcher outside the lock, because it can re-enter + // into VsyncDispatcher::NotifyVsync. + if (needVsync && !wasObservingVsync) { + vsyncSource->AddVsyncDispatcher(this); + } else if (!needVsync && wasObservingVsync) { + vsyncSource->RemoveVsyncDispatcher(this); + } +} + +} // namespace mozilla |