Bug 1959644 - Turn paint into a regular render phase. r=smaug

This reuses the newly introduced infrastructure to track painting, and
avoids painting if rendering is suppressed (such as for view transitions
and so on).

Rendering suppression of an in-process iframe needs more work because
right now we paint in-process iframes as part of the top document's
display list.

Remove some old paint invalidation printf-debugging, which I suspect
nobody has used in years. Profiler markers are strictly better :)

Differential Revision: https://phabricator.services.mozilla.com/D245063
This commit is contained in:
Emilio Cobos Álvarez
2025-04-11 11:22:49 +00:00
parent dee91b864e
commit 327d527c49
13 changed files with 78 additions and 149 deletions

View File

@@ -3687,7 +3687,7 @@ static void PrepareForFullscreenChange(nsIDocShell* aDocShell,
// Since we are suppressing the resize reflow which would originally
// be triggered by view manager, we need to ensure that the refresh
// driver actually schedules a flush, otherwise it may get stuck.
rd->ScheduleViewManagerFlush();
rd->SchedulePaint();
}
if (!aSize.IsEmpty()) {
nsCOMPtr<nsIDocumentViewer> viewer;

View File

@@ -179,8 +179,8 @@ class WebRenderLayerManager final : public WindowRenderer {
void GetFrameUniformity(FrameUniformityData* aOutData) override;
void RegisterPayloads(const nsTArray<CompositionPayload>& aPayload) {
mPayload.AppendElements(aPayload);
void RegisterPayloads(nsTArray<CompositionPayload>&& aPayloads) {
mPayload.AppendElements(std::move(aPayloads));
MOZ_ASSERT(mPayload.Length() < 10000);
}

View File

@@ -1360,11 +1360,6 @@ void PresShell::Destroy() {
StopObservingRefreshDriver();
mObservingStyleFlushes = false;
if (rd->GetPresContext() == GetPresContext()) {
rd->RevokeViewManagerFlush();
rd->ClearHasScheduleFlush();
}
CancelAllPendingReflows();
CancelPostedReflowCallbacks();
@@ -4172,13 +4167,12 @@ bool PresShell::ScrollFrameIntoView(
return didScroll;
}
void PresShell::ScheduleViewManagerFlush() {
void PresShell::SchedulePaint() {
if (MOZ_UNLIKELY(mIsDestroying)) {
return;
}
if (nsPresContext* presContext = GetPresContext()) {
presContext->RefreshDriver()->ScheduleViewManagerFlush();
presContext->RefreshDriver()->SchedulePaint();
}
}

View File

@@ -1344,12 +1344,7 @@ class PresShell final : public nsStubDocumentObserver,
* widget geometry.
*/
MOZ_CAN_RUN_SCRIPT void WillPaint();
/**
* Ensures that the refresh driver is running, and schedules a view
* manager flush on the next tick.
*/
void ScheduleViewManagerFlush();
void SchedulePaint();
// caret handling
NS_IMETHOD SetCaretEnabled(bool aInEnable) override;

View File

@@ -32,7 +32,7 @@ enum class RenderingPhase : uint8_t {
UpdateIntersectionObservations,
// TODO: Record rendering time
// TODO: Mark paint timing
// TODO: Paint.
Paint,
// TODO: Process top layer removals.
Count,
};
@@ -51,6 +51,7 @@ inline constexpr RenderingPhases AllRenderingPhases() {
RenderingPhase::ResizeObservers,
RenderingPhase::ViewTransitionOperations,
RenderingPhase::UpdateIntersectionObservations,
RenderingPhase::Paint,
};
}

View File

@@ -133,7 +133,7 @@ bool nsPresContext::IsDOMPaintEventPending() {
}
nsRootPresContext* drpc = GetRootPresContext();
if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
if (drpc && drpc->mRefreshDriver->IsPaintPending()) {
// Since we're promising that there will be a MozAfterPaint event fired, we
// record an empty invalidation in case display list invalidation doesn't
// invalidate anything further.
@@ -2375,26 +2375,6 @@ void nsPresContext::FireDOMPaintEvent(
static_cast<Event*>(event), this, nullptr);
}
void nsPresContext::NotifyInvalidation(TransactionId aTransactionId,
const nsIntRect& aRect) {
// Prevent values from overflow after DevPixelsToAppUnits().
//
// DevPixelsTopAppUnits() will multiple a factor (60) to the value,
// it may make the result value over the edge (overflow) of max or
// min value of int32_t. Compute the max sized dev pixel rect that
// we can support and intersect with it.
nsIntRect clampedRect = nsIntRect::MaxIntRect();
clampedRect.ScaleInverseRoundIn(AppUnitsPerDevPixel());
clampedRect = clampedRect.Intersect(aRect);
nsRect rect(DevPixelsToAppUnits(clampedRect.x),
DevPixelsToAppUnits(clampedRect.y),
DevPixelsToAppUnits(clampedRect.width),
DevPixelsToAppUnits(clampedRect.height));
NotifyInvalidation(aTransactionId, rect);
}
nsPresContext::TransactionInvalidations* nsPresContext::GetInvalidations(
TransactionId aTransactionId) {
for (TransactionInvalidations& t : mTransactions) {

View File

@@ -952,8 +952,6 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
// be dispatched to MozAfterPaint events when NotifyDidPaintForSubtree is
// called for the transaction id (or any higher id).
void NotifyInvalidation(TransactionId aTransactionId, const nsRect& aRect);
// aRect is in device pixels
void NotifyInvalidation(TransactionId aTransactionId, const nsIntRect& aRect);
void NotifyDidPaintForSubtree(
TransactionId aTransactionId = TransactionId{0},
const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp());

View File

@@ -1255,19 +1255,19 @@ static uint32_t GetFirstFrameDelay(imgIRequest* req) {
return static_cast<uint32_t>(delay);
}
static constexpr std::array<const char*, size_t(RenderingPhase::Count)>
sRenderingPhaseNames = {
"Flush autofocus candidates", // FlushAutoFocusCandidates
"Resize steps", // ResizeSteps
"Scroll steps", // ScrollSteps
"Evaluate media queries and report changes", // EvaluateMediaQueriesAndReportChanges
"Update animations and send events", // UpdateAnimationsAndSendEvents
"Fullscreen steps", // FullscreenSteps
"Animation and video frame callbacks", // AnimationFrameCallbacks
"Update content relevancy", // UpdateContentRelevancy
"Resize observers", // ResizeObservers
"View transition operations", // ViewTransitionOperations
"Update intersection observations", // UpdateIntersectionObservations
static constexpr nsLiteralCString sRenderingPhaseNames[] = {
"Flush autofocus candidates"_ns, // FlushAutoFocusCandidates
"Resize steps"_ns, // ResizeSteps
"Scroll steps"_ns, // ScrollSteps
"Evaluate media queries and report changes"_ns, // EvaluateMediaQueriesAndReportChanges
"Update animations and send events"_ns, // UpdateAnimationsAndSendEvents
"Fullscreen steps"_ns, // FullscreenSteps
"Animation and video frame callbacks"_ns, // AnimationFrameCallbacks
"Update content relevancy"_ns, // UpdateContentRelevancy
"Resize observers"_ns, // ResizeObservers
"View transition operations"_ns, // ViewTransitionOperations
"Update intersection observations"_ns, // UpdateIntersectionObservations
"Paint"_ns, // Paint
};
static_assert(std::size(sRenderingPhaseNames) == size_t(RenderingPhase::Count),
@@ -1282,7 +1282,8 @@ void nsRefreshDriver::RunRenderingPhaseLegacy(RenderingPhase aPhase,
mRenderingPhasesNeeded -= aPhase;
AUTO_PROFILER_LABEL_DYNAMIC_CSTR_RELEVANT_FOR_JS(
"Update the rendering", LAYOUT, sRenderingPhaseNames[size_t(aPhase)]);
"Update the rendering", LAYOUT,
sRenderingPhaseNames[size_t(aPhase)].get());
aCallback();
}
@@ -1427,8 +1428,6 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mThrottled(false),
mNeedToRecomputeVisibility(false),
mTestControllingRefreshes(false),
mViewManagerFlushIsPending(false),
mHasScheduleFlush(false),
mInRefresh(false),
mWaitingForTransaction(false),
mSkippedPaints(false),
@@ -1801,15 +1800,6 @@ void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
}
}
// When switching from an inactive timer to an active timer, the root
// refresh driver is skipped due to being set to the content refresh
// driver's timestamp. In case of EnsureTimerStarted is called from
// ScheduleViewManagerFlush, we should avoid this behavior to flush
// a paint in the same tick on the root refresh driver.
if (aFlags & eNeverAdjustTimer) {
return;
}
// Since the different timers are sampled at different rates, when switching
// timers, the most recent refresh of the new timer may be *before* the
// most recent refresh of the old timer.
@@ -1847,7 +1837,6 @@ uint32_t nsRefreshDriver::ObserverCount() const {
// layout changes can affect media queries on child documents, triggering
// style changes, etc.
sum += mStyleFlushObservers.Length();
sum += mViewManagerFlushIsPending;
sum += mEarlyRunners.Length();
return sum;
}
@@ -1859,8 +1848,7 @@ bool nsRefreshDriver::HasObservers() const {
}
}
return (mViewManagerFlushIsPending && !mThrottled) ||
!mStyleFlushObservers.IsEmpty() || !mEarlyRunners.IsEmpty();
return !mStyleFlushObservers.IsEmpty() || !mEarlyRunners.IsEmpty();
}
void nsRefreshDriver::AppendObserverDescriptionsToString(
@@ -1871,9 +1859,6 @@ void nsRefreshDriver::AppendObserverDescriptionsToString(
kFlushTypeNames[observer.mFlushType]);
}
}
if (mViewManagerFlushIsPending && !mThrottled) {
aStr.AppendLiteral("View manager flush pending, ");
}
if (!mStyleFlushObservers.IsEmpty()) {
aStr.AppendPrintf("%zux Style flush observer, ",
mStyleFlushObservers.Length());
@@ -2655,7 +2640,20 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
UpdateAnimatedImages(previousRefresh, aNowTime);
bool dispatchTasksAfterTick = FlushViewManagerIfNeeded();
bool painted = false;
RunRenderingPhaseLegacy(
RenderingPhase::Paint,
[&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { painted = PaintIfNeeded(); });
if (!painted) {
// No paint happened, discard composition payloads.
mCompositionPayloads.Clear();
mPaintCause = nullptr;
}
if (MOZ_UNLIKELY(!mPresContext || !mPresContext->GetPresShell())) {
return StopTimer();
}
// This needs to happen after DL building since we rely on the raster scales
// being stored in nsSubDocumentFrame.
@@ -2675,10 +2673,10 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
if (mPresContext->IsRoot() && XRE_IsContentProcess() &&
StaticPrefs::gfx_content_always_paint()) {
ScheduleViewManagerFlush();
SchedulePaint();
}
if (dispatchTasksAfterTick && sPendingIdleTasks) {
if (painted && sPendingIdleTasks) {
UniquePtr<AutoTArray<RefPtr<Task>, 8>> tasks(sPendingIdleTasks.forget());
for (RefPtr<Task>& taskWithDelay : *tasks) {
TaskController::Get()->AddTask(taskWithDelay.forget());
@@ -2686,10 +2684,19 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
}
}
bool nsRefreshDriver::FlushViewManagerIfNeeded() {
if (!mViewManagerFlushIsPending || mThrottled) {
// No paint happened, discard composition payloads.
mCompositionPayloads.Clear();
bool nsRefreshDriver::PaintIfNeeded() {
if (mThrottled) {
return false;
}
if (IsPresentingInVR()) {
// Skip the paint in immersive VR mode because whatever we paint here will
// not end up on the screen. The screen is displaying WebGL content from a
// single canvas in that mode.
return false;
}
if (mPresContext->Document()->IsRenderingSuppressed()) {
// If the top level document is suppressed, skip painting altogether.
// TODO(emilio): Deal with this properly for subdocuments.
return false;
}
nsCString transactionId;
@@ -2699,9 +2706,8 @@ bool nsRefreshDriver::FlushViewManagerIfNeeded() {
}
AUTO_PROFILER_MARKER_TEXT(
"ViewManagerFlush", GRAPHICS,
MarkerOptions(
MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
MarkerStack::TakeBacktrace(std::move(mViewManagerFlushCause))),
MarkerOptions(MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
MarkerStack::TakeBacktrace(std::move(mPaintCause))),
transactionId);
// Forward our composition payloads to the layer manager.
@@ -2709,37 +2715,16 @@ bool nsRefreshDriver::FlushViewManagerIfNeeded() {
nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
WindowRenderer* renderer = widget ? widget->GetWindowRenderer() : nullptr;
if (renderer && renderer->AsWebRender()) {
renderer->AsWebRender()->RegisterPayloads(mCompositionPayloads);
renderer->AsWebRender()->RegisterPayloads(
std::move(mCompositionPayloads));
}
mCompositionPayloads.Clear();
}
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Starting ProcessPendingUpdates\n");
}
#endif
mViewManagerFlushIsPending = false;
RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
const bool skipPaint = IsPresentingInVR();
// Skip the paint in immersive VR mode because whatever we paint here will
// not end up on the screen. The screen is displaying WebGL content from a
// single canvas in that mode.
// FIXME(emilio): Should we early return above instead, just like if we're
// throttled or what not?
if (!skipPaint) {
RefPtr<nsViewManager> vm = mPresContext->PresShell()->GetViewManager();
{
PaintTelemetry::AutoRecordPaint record;
vm->ProcessPendingUpdates();
}
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Ending ProcessPendingUpdates\n");
}
#endif
mHasScheduleFlush = false;
return true;
}
@@ -3027,15 +3012,14 @@ bool nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
}
#endif
void nsRefreshDriver::ScheduleViewManagerFlush() {
void nsRefreshDriver::SchedulePaint() {
NS_ASSERTION(mPresContext && mPresContext->IsRoot(),
"Should only schedule view manager flush on root prescontexts");
mViewManagerFlushIsPending = true;
if (!mViewManagerFlushCause) {
mViewManagerFlushCause = profiler_capture_backtrace();
if (!mPaintCause) {
mPaintCause = profiler_capture_backtrace();
}
mHasScheduleFlush = true;
EnsureTimerStarted(eNeverAdjustTimer);
ScheduleRenderingPhase(RenderingPhase::Paint);
EnsureTimerStarted();
}
/* static */

View File

@@ -105,7 +105,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
mozilla::FlushType aFlushType);
MOZ_CAN_RUN_SCRIPT void FlushLayoutOnPendingDocsAndFixUpFocus();
/**
@@ -167,16 +166,14 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
EnsureTimerStarted();
}
/**
* Remember whether our presshell's view manager needs a flush
*/
void ScheduleViewManagerFlush();
void RevokeViewManagerFlush() { mViewManagerFlushIsPending = false; }
bool ViewManagerFlushIsPending() { return mViewManagerFlushIsPending; }
bool HasScheduleFlush() { return mHasScheduleFlush; }
void ClearHasScheduleFlush() { mHasScheduleFlush = false; }
// Remember whether our presshell's view manager needs a flush
void SchedulePaint();
bool IsPaintPending() const {
return mRenderingPhasesNeeded.contains(mozilla::RenderingPhase::Paint);
}
// Returns true if a paint actually occurred.
MOZ_CAN_RUN_SCRIPT bool FlushViewManagerIfNeeded();
MOZ_CAN_RUN_SCRIPT bool PaintIfNeeded();
/**
* Schedule a frame visibility update "soon", subject to the heuristics and
@@ -448,7 +445,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
eNone = 0,
eForceAdjustTimer = 1 << 0,
eAllowTimeToGoBackwards = 1 << 1,
eNeverAdjustTimer = 1 << 2,
};
void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone);
void StopTimer();
@@ -530,21 +526,11 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// flush since the last time we did it.
const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> mViewManagerFlushCause;
mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> mPaintCause;
bool mThrottled : 1;
bool mNeedToRecomputeVisibility : 1;
bool mTestControllingRefreshes : 1;
// These two fields are almost the same, the only difference is that
// mViewManagerFlushIsPending gets cleared right before calling
// ProcessPendingUpdates, and mHasScheduleFlush gets cleared right after
// calling ProcessPendingUpdates. It is important that mHasScheduleFlush
// only gets cleared after, but it's not clear if mViewManagerFlushIsPending
// needs to be cleared before.
bool mViewManagerFlushIsPending : 1;
bool mHasScheduleFlush : 1;
bool mInRefresh : 1;
// True if the refresh driver is suspended waiting for transaction

View File

@@ -8028,7 +8028,7 @@ static void SchedulePaintInternal(
return;
}
pres->PresShell()->ScheduleViewManagerFlush();
pres->PresShell()->SchedulePaint();
if (aType == nsIFrame::PAINT_DEFAULT) {
aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE);

View File

@@ -2303,11 +2303,11 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
aFlags & PAINT_COMPOSITE_OFFSCREEN);
}
if (presContext->RefreshDriver()->HasScheduleFlush()) {
if (presContext->RefreshDriver()->IsInRefresh() ||
presContext->RefreshDriver()->IsPaintPending()) {
presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
frame->GetRect());
}
return;
}

View File

@@ -19,9 +19,7 @@
#include "nsIWidget.h"
#include "nsViewManager.h"
#include "nsIFrame.h"
#include "nsPresArena.h"
#include "nsXULPopupManager.h"
#include "nsIScreen.h"
#include "nsIWidgetListener.h"
#include "nsContentUtils.h" // for nsAutoScriptBlocker
#include "nsDocShell.h"
@@ -955,7 +953,7 @@ void nsView::DidCompositeWindow(mozilla::layers::TransactionId aTransactionId,
void nsView::RequestRepaint() {
if (PresShell* presShell = mViewManager->GetPresShell()) {
presShell->ScheduleViewManagerFlush();
presShell->SchedulePaint();
}
}

View File

@@ -434,7 +434,7 @@ void nsViewManager::PostPendingUpdate() {
rootVM->mHasPendingWidgetGeometryChanges = true;
if (rootVM->mPresShell) {
rootVM->mPresShell->SetNeedLayoutFlush();
rootVM->mPresShell->ScheduleViewManagerFlush();
rootVM->mPresShell->SchedulePaint();
}
}
@@ -778,17 +778,10 @@ void nsViewManager::ProcessPendingUpdates() {
// Flush things like reflows by calling WillPaint on observer presShells.
if (mPresShell) {
mPresShell->GetPresContext()->RefreshDriver()->RevokeViewManagerFlush();
RefPtr<nsViewManager> strongThis(this);
CallWillPaintOnObservers();
ProcessPendingUpdatesForView(mRootView, true);
if (mPresShell) {
if (nsPresContext* pc = mPresShell->GetPresContext()) {
pc->RefreshDriver()->ClearHasScheduleFlush();
}
}
}
}