gecko-dev/widget/VsyncDispatcher.cpp

262 lines
7.9 KiB
C++

/* -*- 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