/* -*- 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 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 vsyncControl = NewRunnableMethod( "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 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> 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( "VsyncDispatcher::NotifyMainThreadObservers", this, &VsyncDispatcher::NotifyMainThreadObservers, aVsync)); } } void VsyncDispatcher::NotifyMainThreadObservers(VsyncEvent aEvent) { MOZ_ASSERT(NS_IsMainThread()); nsTArray> 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 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