Bug 1708325 - Change frame throttling code in nsRefreshDriver to explicitly track the pending frames. r=mstange

This makes it easier to understand and will make it easier to not include 'extra' frames (as we're hoping they will coalesce with the original frame on the compositor).

Differential Revision: https://phabricator.services.mozilla.com/D113736
This commit is contained in:
Matt Woodrow 2021-05-10 00:00:50 +00:00
Родитель 9bdd0264e6
Коммит e76f264866
12 изменённых файлов: 106 добавлений и 96 удалений

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

@ -349,23 +349,25 @@ mozilla::ipc::IPCResult CompositorBridgeChild::RecvInvalidateLayers(
}
mozilla::ipc::IPCResult CompositorBridgeChild::RecvDidComposite(
const LayersId& aId, const TransactionId& aTransactionId,
const LayersId& aId, const nsTArray<TransactionId>& aTransactionIds,
const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) {
// Hold a reference to keep texture pools alive. See bug 1387799
const auto texturePools = mTexturePools.Clone();
if (mLayerManager) {
MOZ_ASSERT(!aId.IsValid());
MOZ_ASSERT(mLayerManager->GetBackendType() ==
LayersBackend::LAYERS_CLIENT ||
mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR);
// Hold a reference to keep LayerManager alive. See Bug 1242668.
RefPtr<LayerManager> m = mLayerManager;
m->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd);
} else if (aId.IsValid()) {
RefPtr<dom::BrowserChild> child = dom::BrowserChild::GetFrom(aId);
if (child) {
child->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd);
for (const auto& id : aTransactionIds) {
if (mLayerManager) {
MOZ_ASSERT(!aId.IsValid());
MOZ_ASSERT(mLayerManager->GetBackendType() ==
LayersBackend::LAYERS_CLIENT ||
mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR);
// Hold a reference to keep LayerManager alive. See Bug 1242668.
RefPtr<LayerManager> m = mLayerManager;
m->DidComposite(id, aCompositeStart, aCompositeEnd);
} else if (aId.IsValid()) {
RefPtr<dom::BrowserChild> child = dom::BrowserChild::GetFrom(aId);
if (child) {
child->DidComposite(id, aCompositeStart, aCompositeEnd);
}
}
}

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

@ -92,10 +92,9 @@ class CompositorBridgeChild final : public PCompositorBridgeChild,
// process). This may only be called on the main thread.
static bool CompositorIsInGPUProcess();
mozilla::ipc::IPCResult RecvDidComposite(const LayersId& aId,
const TransactionId& aTransactionId,
const TimeStamp& aCompositeStart,
const TimeStamp& aCompositeEnd);
mozilla::ipc::IPCResult RecvDidComposite(
const LayersId& aId, const nsTArray<TransactionId>& aTransactionIds,
const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd);
mozilla::ipc::IPCResult RecvNotifyFrameStats(
nsTArray<FrameStats>&& aFrameStats);

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

@ -332,7 +332,6 @@ CompositorBridgeParent::CompositorBridgeParent(
mWidget(nullptr),
mScale(aScale),
mVsyncRate(aVsyncRate),
mPendingTransaction{0},
mPaused(false),
mHaveCompositionRecorder(false),
mIsForcedFirstPaint(false),
@ -1164,9 +1163,9 @@ void CompositorBridgeParent::ShadowLayersUpdated(
// The transaction ID might get reset to 1 if the page gets reloaded, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1145295#c41
// Otherwise, it should be continually increasing.
MOZ_ASSERT(aInfo.id() == TransactionId{1} ||
aInfo.id() > mPendingTransaction);
mPendingTransaction = aInfo.id();
MOZ_ASSERT(aInfo.id() == TransactionId{1} || mPendingTransactions.IsEmpty() ||
aInfo.id() > mPendingTransactions.LastElement());
mPendingTransactions.AppendElement(aInfo.id());
mRefreshStartTime = aInfo.refreshStart();
mTxnStartTime = aInfo.transactionStart();
mFwdTime = aInfo.fwdTime();
@ -2202,10 +2201,10 @@ void CompositorBridgeParent::DidComposite(const VsyncId& aId,
if (mWrBridge) {
MOZ_ASSERT(false); // This should never get called for a WR compositor
} else {
NotifyDidComposite(mPendingTransaction, aId, aCompositeStart,
NotifyDidComposite(mPendingTransactions, aId, aCompositeStart,
aCompositeEnd);
#if defined(ENABLE_FRAME_LATENCY_LOG)
if (mPendingTransaction.IsValid()) {
if (!mPendingTransactions.IsEmpty()) {
if (mRefreshStartTime) {
int32_t latencyMs =
lround((aCompositeEnd - mRefreshStartTime).ToMilliseconds());
@ -2226,7 +2225,7 @@ void CompositorBridgeParent::DidComposite(const VsyncId& aId,
mTxnStartTime = TimeStamp();
mFwdTime = TimeStamp();
#endif
mPendingTransaction = TransactionId{0};
mPendingTransactions.Clear();
}
}
@ -2329,14 +2328,15 @@ void CompositorBridgeParent::NotifyPipelineRendered(
wrBridge->RemoveEpochDataPriorTo(aEpoch);
nsTArray<FrameStats> stats;
nsTArray<TransactionId> transactions;
RefPtr<UiCompositorControllerParent> uiController =
UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID);
Maybe<TransactionId> transactionId = wrBridge->FlushTransactionIdsForEpoch(
wrBridge->FlushTransactionIdsForEpoch(
aEpoch, aCompositeStartId, aCompositeStart, aRenderStart, aCompositeEnd,
uiController, aStats, &stats);
if (!transactionId) {
uiController, aStats, stats, transactions);
if (transactions.IsEmpty()) {
MOZ_ASSERT(stats.IsEmpty());
return;
}
@ -2344,7 +2344,7 @@ void CompositorBridgeParent::NotifyPipelineRendered(
MaybeDeclareStable();
LayersId layersId = isRoot ? LayersId{0} : wrBridge->GetLayersId();
Unused << compBridge->SendDidComposite(layersId, *transactionId,
Unused << compBridge->SendDidComposite(layersId, transactions,
aCompositeStart, aCompositeEnd);
if (!stats.IsEmpty()) {
@ -2357,16 +2357,15 @@ CompositorBridgeParent::GetAsyncImagePipelineManager() const {
return mAsyncImageManager;
}
void CompositorBridgeParent::NotifyDidComposite(TransactionId aTransactionId,
VsyncId aId,
TimeStamp& aCompositeStart,
TimeStamp& aCompositeEnd) {
void CompositorBridgeParent::NotifyDidComposite(
const nsTArray<TransactionId>& aTransactionIds, VsyncId aId,
TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd) {
MOZ_ASSERT(!mWrBridge,
"We should be going through NotifyDidRender and "
"NotifyPipelineRendered instead");
MaybeDeclareStable();
Unused << SendDidComposite(LayersId{0}, aTransactionId, aCompositeStart,
Unused << SendDidComposite(LayersId{0}, aTransactionIds, aCompositeStart,
aCompositeEnd);
if (mLayerManager) {

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

@ -772,8 +772,9 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase,
void DidComposite(const VsyncId& aId, TimeStamp& aCompositeStart,
TimeStamp& aCompositeEnd);
void NotifyDidComposite(TransactionId aTransactionId, VsyncId aId,
TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd);
void NotifyDidComposite(const nsTArray<TransactionId>& aTransactionIds,
VsyncId aId, TimeStamp& aCompositeStart,
TimeStamp& aCompositeEnd);
// The indirect layer tree lock must be held before calling this function.
// Callback should take (LayerTreeState* aState, const LayersId& aLayersId)
@ -796,7 +797,7 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase,
CSSToLayoutDeviceScale mScale;
TimeDuration mVsyncRate;
TransactionId mPendingTransaction;
AutoTArray<TransactionId, 2> mPendingTransactions;
TimeStamp mRefreshStartTime;
TimeStamp mTxnStartTime;
TimeStamp mFwdTime;

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

@ -395,10 +395,10 @@ void ContentCompositorBridgeParent::DidCompositeLocked(
TimeStamp& aCompositeEnd) {
sIndirectLayerTreesLock->AssertCurrentThreadOwns();
if (LayerTransactionParent* layerTree = sIndirectLayerTrees[aId].mLayerTree) {
TransactionId transactionId =
layerTree->FlushTransactionId(aVsyncId, aCompositeEnd);
if (transactionId.IsValid()) {
Unused << SendDidComposite(aId, transactionId, aCompositeStart,
nsTArray<TransactionId> transactions;
layerTree->FlushPendingTransactions(aVsyncId, aCompositeEnd, transactions);
if (!transactions.IsEmpty()) {
Unused << SendDidComposite(aId, transactions, aCompositeStart,
aCompositeEnd);
}
} else if (sIndirectLayerTrees[aId].mWrBridge) {

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

@ -888,11 +888,11 @@ void LayerTransactionParent::SetPendingTransactionId(
aTxnEndTime, aFwdTime, aURL, aContainsSVG});
}
TransactionId LayerTransactionParent::FlushTransactionId(
const VsyncId& aCompositeId, TimeStamp& aCompositeEnd) {
TransactionId id;
void LayerTransactionParent::FlushPendingTransactions(
const VsyncId& aCompositeId, TimeStamp& aCompositeEnd,
nsTArray<TransactionId>& aOutTransactions) {
for (auto& transaction : mPendingTransactions) {
id = transaction.mId;
aOutTransactions.AppendElement(transaction.mId);
if (mId.IsValid() && transaction.mId.IsValid() && !mVsyncRate.IsZero()) {
RecordContentFrameTime(
transaction.mTxnVsyncId, transaction.mVsyncStartTime,
@ -924,7 +924,6 @@ TransactionId LayerTransactionParent::FlushTransactionId(
}
mPendingTransactions.Clear();
return id;
}
void LayerTransactionParent::SendAsyncMessage(

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

@ -79,8 +79,8 @@ class LayerTransactionParent final : public PLayerTransactionParent,
const TimeStamp& aTxnEndTime, bool aContainsSVG,
const nsCString& aURL,
const TimeStamp& aFwdTime);
TransactionId FlushTransactionId(const VsyncId& aId,
TimeStamp& aCompositeEnd);
void FlushPendingTransactions(const VsyncId& aId, TimeStamp& aCompositeEnd,
nsTArray<TransactionId>& aOutTransactions);
// CompositableParentManager
void SendAsyncMessage(

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

@ -118,7 +118,7 @@ child:
// transactionId is the id of the transaction before this composite, or 0
// if there was no transaction since the last composite.
[Priority=mediumhigh] async DidComposite(LayersId id,
TransactionId transactionId,
TransactionId[] transactionId,
TimeStamp compositeStart,
TimeStamp compositeEnd);

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

@ -2369,12 +2369,12 @@ static void RecordPaintPhaseTelemetry(wr::RendererStats* aStats) {
RecordKey("fb"_ns, frameBuild);
}
Maybe<TransactionId> WebRenderBridgeParent::FlushTransactionIdsForEpoch(
void WebRenderBridgeParent::FlushTransactionIdsForEpoch(
const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
wr::RendererStats* aStats, nsTArray<FrameStats>* aOutputStats) {
Maybe<TransactionId> id = Nothing();
wr::RendererStats* aStats, nsTArray<FrameStats>& aOutputStats,
nsTArray<TransactionId>& aOutputTransactions) {
while (!mPendingTransactionIds.empty()) {
const auto& transactionId = mPendingTransactionIds.front();
@ -2398,7 +2398,7 @@ Maybe<TransactionId> WebRenderBridgeParent::FlushTransactionIdsForEpoch(
RecordPaintPhaseTelemetry(aStats);
if (contentFrameTime > 200) {
aOutputStats->AppendElement(FrameStats(
aOutputStats.AppendElement(FrameStats(
transactionId.mId, aCompositeStartTime, aRenderStartTime, aEndTime,
contentFrameTime,
aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
@ -2434,10 +2434,9 @@ Maybe<TransactionId> WebRenderBridgeParent::FlushTransactionIdsForEpoch(
RecordCompositionPayloadsPresented(aEndTime, transactionId.mPayloads);
id = Some(transactionId.mId);
aOutputTransactions.AppendElement(transactionId.mId);
mPendingTransactionIds.pop_front();
}
return id;
}
LayersId WebRenderBridgeParent::GetLayersId() const {

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

@ -204,12 +204,12 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent,
nsTArray<CompositionPayload>&& aPayloads,
const bool aUseForTelemetry = true);
TransactionId LastPendingTransactionId();
Maybe<TransactionId> FlushTransactionIdsForEpoch(
void FlushTransactionIdsForEpoch(
const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
wr::RendererStats* aStats = nullptr,
nsTArray<FrameStats>* aOutputStats = nullptr);
wr::RendererStats* aStats, nsTArray<FrameStats>& aOutputStats,
nsTArray<TransactionId>& aOutputTransactions);
void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch,
const TimeStamp& aEndTime);

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

@ -1108,8 +1108,6 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mPresContext(aPresContext),
mRootRefresh(nullptr),
mNextTransactionId{0},
mOutstandingTransactionId{0},
mCompletedTransaction{0},
mFreezeCount(0),
mThrottledFrameRequestInterval(
TimeDuration::FromMilliseconds(GetThrottledTimerInterval())),
@ -1125,6 +1123,7 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mResizeSuppressed(false),
mNotifyDOMContentFlushed(false),
mNeedToUpdateIntersectionObservations(false),
mInNormalTick(false),
mWarningThreshold(REFRESH_WAIT_WARNING) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPresContext,
@ -1186,7 +1185,7 @@ void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
void nsRefreshDriver::RestoreNormalRefresh() {
mTestControllingRefreshes = false;
EnsureTimerStarted(eAllowTimeToGoBackwards);
mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId;
mPendingTransactions.Clear();
}
TimeStamp nsRefreshDriver::MostRecentRefresh(bool aEnsureTimerStarted) const {
@ -1986,6 +1985,8 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) {
return;
}
auto cleanupInExtraTick = MakeScopeExit([&] { mInNormalTick = false; });
mInNormalTick = true;
bool isPresentingInVR = false;
#if defined(MOZ_WIDGET_ANDROID)
@ -2497,14 +2498,21 @@ void nsRefreshDriver::FinishedWaitingForTransaction() {
mozilla::layers::TransactionId nsRefreshDriver::GetTransactionId(
bool aThrottle) {
mOutstandingTransactionId = mOutstandingTransactionId.Next();
mNextTransactionId = mNextTransactionId.Next();
LOG("[%p] Allocating transaction id %" PRIu64, this, mNextTransactionId.mId);
if (aThrottle && mOutstandingTransactionId - mCompletedTransaction >= 2 &&
!mWaitingForTransaction && !mTestControllingRefreshes) {
mWaitingForTransaction = true;
mSkippedPaints = false;
mWarningThreshold = 1;
// If this a paint from within a normal tick, and the caller hasn't explicitly
// asked for it to skip being throttled, then record this transaction as
// pending and maybe disable painting until some transactions are processed.
if (aThrottle && mInNormalTick) {
mPendingTransactions.AppendElement(mNextTransactionId);
if (TooManyPendingTransactions() && !mWaitingForTransaction &&
!mTestControllingRefreshes) {
LOG("[%p] Hit max pending transaction limit, entering wait mode", this);
mWaitingForTransaction = true;
mSkippedPaints = false;
mWarningThreshold = 1;
}
}
return mNextTransactionId;
@ -2517,8 +2525,11 @@ mozilla::layers::TransactionId nsRefreshDriver::LastTransactionId() const {
void nsRefreshDriver::RevokeTransactionId(
mozilla::layers::TransactionId aTransactionId) {
MOZ_ASSERT(aTransactionId == mNextTransactionId);
if (mOutstandingTransactionId - mCompletedTransaction == 2 &&
mWaitingForTransaction) {
LOG("[%p] Revoking transaction id %" PRIu64, this, aTransactionId.mId);
if (AtPendingTransactionLimit() &&
mPendingTransactions.Contains(aTransactionId) && mWaitingForTransaction) {
LOG("[%p] No longer over pending transaction limit, leaving wait state",
this);
MOZ_ASSERT(!mSkippedPaints,
"How did we skip a paint when we're in the middle of one?");
FinishedWaitingForTransaction();
@ -2530,21 +2541,21 @@ void nsRefreshDriver::RevokeTransactionId(
if (pc) {
pc->NotifyRevokingDidPaint(aTransactionId);
}
// Revert the outstanding transaction since we're no longer waiting on it to
// be completed, but don't revert mNextTransactionId since we can't use the id
// again.
mOutstandingTransactionId = mOutstandingTransactionId.Prev();
// Remove aTransactionId from the set of outstanding transactions since we're
// no longer waiting on it to be completed, but don't revert
// mNextTransactionId since we can't use the id again.
mPendingTransactions.RemoveElement(aTransactionId);
}
void nsRefreshDriver::ClearPendingTransactions() {
mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId;
LOG("[%p] ClearPendingTransactions", this);
mPendingTransactions.Clear();
mWaitingForTransaction = false;
}
void nsRefreshDriver::ResetInitialTransactionId(
mozilla::layers::TransactionId aTransactionId) {
mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId =
aTransactionId;
mNextTransactionId = aTransactionId;
}
mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
@ -2555,20 +2566,12 @@ mozilla::TimeStamp nsRefreshDriver::GetVsyncStart() { return mTickVsyncTime; }
void nsRefreshDriver::NotifyTransactionCompleted(
mozilla::layers::TransactionId aTransactionId) {
if (aTransactionId > mCompletedTransaction) {
if (mOutstandingTransactionId - mCompletedTransaction > 1 &&
mWaitingForTransaction) {
mCompletedTransaction = aTransactionId;
FinishedWaitingForTransaction();
} else {
mCompletedTransaction = aTransactionId;
}
}
// If completed transaction id get ahead of outstanding id, reset to distance
// id.
if (mCompletedTransaction > mOutstandingTransactionId) {
mOutstandingTransactionId = mCompletedTransaction;
LOG("[%p] Completed transaction id %" PRIu64, this, aTransactionId.mId);
mPendingTransactions.RemoveElement(aTransactionId);
if (mWaitingForTransaction && !TooManyPendingTransactions()) {
LOG("[%p] No longer over pending transaction limit, leaving wait state",
this);
FinishedWaitingForTransaction();
}
}
@ -2596,6 +2599,9 @@ bool nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) {
mWarningThreshold *= 2;
}
LOG("[%p] Over max pending transaction limit when trying to paint, "
"skipping",
this);
mSkippedPaints = true;
return true;
}

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

@ -480,6 +480,13 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
bool CanDoCatchUpTick();
bool AtPendingTransactionLimit() {
return mPendingTransactions.Length() == 2;
}
bool TooManyPendingTransactions() {
return mPendingTransactions.Length() >= 2;
}
mozilla::RefreshDriverTimer* ChooseTimer();
mozilla::RefreshDriverTimer* mActiveTimer;
RefPtr<mozilla::RefreshDriverTimer> mOwnTimer;
@ -492,13 +499,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// The most recently allocated transaction id.
TransactionId mNextTransactionId;
// This number is mCompletedTransaction + (pending transaction count).
// When we revoke a transaction id, we revert this number (since it's
// no longer outstanding), but not mNextTransactionId (since we don't
// want to reuse the number).
TransactionId mOutstandingTransactionId;
// The most recently completed transaction id.
TransactionId mCompletedTransaction;
AutoTArray<TransactionId, 3> mPendingTransactions;
uint32_t mFreezeCount;
@ -549,6 +550,10 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// all our documents.
bool mNeedToUpdateIntersectionObservations : 1;
// True if we're currently within the scope of Tick() handling a normal
// (timer-driven) tick.
bool mInNormalTick : 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.