зеркало из https://github.com/mozilla/gecko-dev.git
363 строки
12 KiB
C++
363 строки
12 KiB
C++
/* -*- 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 <stdio.h> // for fprintf, stdout
|
|
#include <stdint.h> // 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<CancelableRunnable> task = NewCancelableRunnableMethod<TimeStamp>(
|
|
"layers::CompositorVsyncScheduler::Composite",
|
|
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<CancelableRunnable> task = NewCancelableRunnableMethod(
|
|
"layers::CompositorVsyncScheduler::SetNeedsComposite",
|
|
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<CancelableRunnable> 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
|