Bug 1708325 - Allow doing an extra refresh driver tick for user input events. r=mstange,bas

Differential Revision: https://phabricator.services.mozilla.com/D113737
This commit is contained in:
Matt Woodrow 2021-05-09 22:35:44 +00:00
Родитель 8daf5cd7e6
Коммит fd7a809e96
4 изменённых файлов: 134 добавлений и 5 удалений

Просмотреть файл

@ -1013,6 +1013,17 @@ nsresult EventDispatcher::Dispatch(nsISupports* aTarget,
chain[i].PreHandleEvent(preVisitor);
}
RefPtr<nsRefreshDriver> refreshDriver;
if (aPresContext && aPresContext->GetRootPresContext() &&
aEvent->IsTrusted() &&
(aEvent->mMessage == eKeyPress ||
aEvent->mMessage == eMouseClick)) {
refreshDriver = aPresContext->GetRootPresContext()->RefreshDriver();
if (refreshDriver) {
refreshDriver->EnterUserInputProcessing();
}
}
clearTargets = ShouldClearTargets(aEvent);
// Handle the chain.
@ -1127,6 +1138,10 @@ nsresult EventDispatcher::Dispatch(nsISupports* aTarget,
break;
}
}
if (refreshDriver) {
driver->ExitUserInputProcessing();
}
}
preVisitor.mEventStatus = postVisitor.mEventStatus;

Просмотреть файл

@ -771,6 +771,12 @@ class VsyncRefreshDriverTimer : public RefreshDriverTimer {
void RunRefreshDrivers(VsyncId aId, TimeStamp aTimeStamp) {
Tick(aId, aTimeStamp);
for (auto& driver : mContentRefreshDrivers) {
driver->FinishedVsyncTick();
}
for (auto& driver : mRootRefreshDrivers) {
driver->FinishedVsyncTick();
}
}
// When using local vsync source, we keep a strong ref to it here to ensure
@ -1124,6 +1130,7 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mNotifyDOMContentFlushed(false),
mNeedToUpdateIntersectionObservations(false),
mInNormalTick(false),
mAttemptedExtraTickSinceLastVsync(false),
mWarningThreshold(REFRESH_WAIT_WARNING) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPresContext,
@ -1402,6 +1409,36 @@ bool nsRefreshDriver::CanDoCatchUpTick() {
return true;
}
bool nsRefreshDriver::CanDoExtraTick() {
// Only allow one extra tick per normal vsync tick.
if (mAttemptedExtraTickSinceLastVsync) {
return false;
}
// If we don't have a timer, or we didn't tick on the timer's
// refresh then we can't do an 'extra' tick (but we may still
// do a catch up tick).
if (!mActiveTimer ||
mActiveTimer->MostRecentRefresh() != mMostRecentRefresh) {
return false;
}
// Grab the current timestamp before checking the tick hint to be sure
// sure that it's equal or smaller than the value used within checking
// the tick hint.
TimeStamp now = TimeStamp::Now();
Maybe<TimeStamp> nextTick = mActiveTimer->GetNextTickHint();
int32_t minimumRequiredTime = StaticPrefs::layout_extra_tick_minimum_ms();
// If there's less than 4 milliseconds until the next tick, it's probably
// not worth trying to catch up.
if (minimumRequiredTime < 0 || !nextTick ||
(*nextTick - now) < TimeDuration::FromMilliseconds(minimumRequiredTime)) {
return false;
}
return true;
}
void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
// FIXME: Bug 1346065: We should also assert the case where we have
// STYLO_THREADS=1.
@ -1416,7 +1453,31 @@ void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
}
// will it already fire, and no other changes needed?
if (mActiveTimer && !(aFlags & eForceAdjustTimer)) return;
if (mActiveTimer && !(aFlags & eForceAdjustTimer)) {
// If we're being called from within a user input handler, and we think
// there's time to rush an extra tick immediately, then schedule a runnable
// to run the extra tick.
if (mUserInputProcessingCount && CanDoExtraTick()) {
RefPtr<nsRefreshDriver> self = this;
NS_DispatchToCurrentThreadQueue(
NS_NewRunnableFunction(
"RefreshDriver::EnsureTimerStarted::extra",
[self]() -> void {
// Re-check if we can still do an extra tick, in case anything
// changed while the runnable was pending.
if (self->CanDoExtraTick()) {
PROFILER_MARKER_UNTYPED("ExtraRefreshDriverTick", GRAPHICS);
LOG("[%p] Doing extra tick for user input", self.get());
self->mAttemptedExtraTickSinceLastVsync = true;
self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
self->mActiveTimer->MostRecentRefresh(),
IsExtraTick::Yes);
}
}),
EventQueuePriority::Vsync);
}
return;
}
if (IsFrozen() || !mPresContext) {
// If we don't want to start it now, or we've been disconnected.
@ -1456,6 +1517,7 @@ void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
// Re-check if we can still do a catch-up, in case anything
// changed while the runnable was pending.
if (self->CanDoCatchUpTick()) {
LOG("[%p] Doing catch up tick", self.get());
self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
self->mActiveTimer->MostRecentRefresh());
}
@ -1965,7 +2027,8 @@ static CallState ReduceAnimations(Document& aDocument) {
return CallState::Continue;
}
void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
IsExtraTick aIsExtraTick /* = No */) {
MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
"Shouldn't have a JSContext on the stack");
@ -1982,11 +2045,14 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
// go forward in time, not backwards. To prevent the refresh
// driver from going back in time, just skip this tick and
// wait until the next tick.
if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) {
// If this is an 'extra' tick, then we expect it to be using the same
// vsync id and timestamp as the original tick, so also allow those.
if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes &&
aIsExtraTick == IsExtraTick::No) {
return;
}
auto cleanupInExtraTick = MakeScopeExit([&] { mInNormalTick = false; });
mInNormalTick = true;
mInNormalTick = aIsExtraTick != IsExtraTick::Yes;
bool isPresentingInVR = false;
#if defined(MOZ_WIDGET_ANDROID)

Просмотреть файл

@ -146,6 +146,16 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
bool AddImageRequest(imgIRequest* aRequest);
void RemoveImageRequest(imgIRequest* aRequest);
/**
* Marks that we're currently in the middle of processing user input.
* Called by EventDispatcher when it's handling an input event.
*/
void EnterUserInputProcessing() { mUserInputProcessingCount++; }
void ExitUserInputProcessing() {
MOZ_ASSERT(mUserInputProcessingCount > 0);
mUserInputProcessingCount--;
}
/**
* Add / remove presshells which have pending resize event.
*/
@ -408,6 +418,10 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext);
void FlushForceNotifyContentfulPaintPresContext();
// Mark that we've just run a tick from vsync, used to throttle 'extra'
// paints to one per vsync (see CanDoExtraTick).
void FinishedVsyncTick() { mAttemptedExtraTickSinceLastVsync = false; }
private:
typedef nsTArray<RefPtr<VVPResizeEvent>> VisualViewportResizeEventArray;
typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
@ -443,7 +457,13 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Tick(mozilla::VsyncId aId, mozilla::TimeStamp aNowTime);
enum class IsExtraTick {
No,
Yes,
};
void Tick(mozilla::VsyncId aId, mozilla::TimeStamp aNowTime,
IsExtraTick aIsExtraTick = IsExtraTick::No);
enum EnsureTimerStartedFlags {
eNone = 0,
@ -478,7 +498,18 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
void FinishedWaitingForTransaction();
/**
* Returns true if we didn't tick on the most recent vsync, but we think
* we could run one now instead in order to reduce latency.
*/
bool CanDoCatchUpTick();
/**
* Returns true if we think it's possible to run an repeat tick (between
* vsyncs) to hopefully replace the original tick's paint on the compositor.
* We allow this sometimes for tick requests coming for user input handling
* to reduce latency.
*/
bool CanDoExtraTick();
bool AtPendingTransactionLimit() {
return mPendingTransactions.Length() == 2;
@ -502,6 +533,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
AutoTArray<TransactionId, 3> mPendingTransactions;
uint32_t mFreezeCount;
uint32_t mUserInputProcessingCount = 0;
// How long we wait between ticks for throttled (which generally means
// non-visible) documents registered with a non-throttled refresh driver.
@ -554,6 +586,10 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// (timer-driven) tick.
bool mInNormalTick : 1;
// True if we attempted an extra tick (see CanDoExtraTick) since the last
// vsync and thus shouldn't allow another.
bool mAttemptedExtraTickSinceLastVsync : 1;
// Number of seconds that the refresh driver is blocked waiting for a
// compositor transaction to be completed before we append a note to the gfx
// critical log. The number is doubled every time the threshold is hit.

Просмотреть файл

@ -7165,6 +7165,18 @@
value: 1
mirror: always
# The minimum amount of time (milliseconds) required to be remaining
# in the current vsync interval for us to attempt an extra tick, or
# <0 to disable extra ticks entirely.
- name: layout.extra-tick.minimum-ms
type: int32_t
#if defined(NIGHTLY_BUILD)
value: 4
#else
value: -1
#endif
mirror: always
# Enable/disable interruptible reflow, which allows reflows to stop
# before completion (and display the partial results) when user events
# are pending.