Closed Bug 107827 Opened 23 years ago Closed 23 years ago

Mac is over-painting during page load

Categories

(Core :: XUL, defect)

PowerPC
Mac System 9.x
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla0.9.8

People

(Reporter: bryner, Assigned: sfraser_bugs)

Details

(Keywords: perf)

Attachments

(2 files)

Spun off from bug 97895.

Invalidates happening on hidden nsWindows during paint suppression are causing
painting to happen on visible widgets.  This is because of the way Invalidates
are translated to coordinates relative to the toplevel window, losing the key
information of exactly which widget was invalidated.

Simon, feel free to reassign as needed (but not to me, I don't have a Mac).
So I'm trying to figure out how we can make this work on Mac, and not having much 
success. Each widget could maintain a dirty region, and we could intersect this 
with incoming updates to decide what to paint. But that doesn't do much for 
widgets with large overlapping areas, which is most of the problem.

I really think a cleaner solution to this is to keep widgets/views for hidden 
content viewers really hidden, as I described in bug 97895.
Status: NEW → ASSIGNED
do dcone's front->back painting changes help this at all (bug 69010)?
I don't think so.

I did think of one thing I could do. I could give each widget a 'pending update' 
flag, and then only draw a widget in response to an update event if the flag is 
set. That way, we may invalidate large areas of the screen, but we could avoid 
repainting it all unnecessarily.
Well, the 'pending update' flag doesn't work, because I can't tell if the 
incoming update event was OS-generated (e.g. window exposed) or is the result of 
an earlier Invalidate() call. So I can't ignore updates based on whether the 
window has had any known invalidates.
If you need to know whether it is an OS event, you can pass a boolean to 
HandleUpdateEvent(), false by default and true when called from 
nsMacEventHandler::UpdateEvent().
That's not it; we'll get update events from the OS as a result of two kinds of 
invalidates:
1. Internal calls to InvalRect/Rgn
2. OS-level events like window expose (Windowshade, user unobscuring part of the
   window).

and, when we receive the update event, we can't distinguish its cause(s). This 
means that we can't know to ignore udpate events that happen as a result of 
invalidates on certain widgets.
I tried a solution where when we call Invalidate(rect) of Invalidate(rgn) for an 
invisible widget, we accumulate the rect or the rgn into a member variable.  When 
the widget is made visible, the whole area is really invalidated.

It doesn't work.  When loading a simple page, we have only 3 such calls.  They 
happen right before "Document loaded successfully" but by that time we have 
already repainted the old page.  For those familiar with the traces from bug 
69010, here is what it looks like (formatted to 80 chars/line):

----
UpdateWidget()                  parent  rect top=68 left=10  width=737 height=372
New code     updating  visible  level 1 rect top=68 left=10  width=737 height=372
New code NOT updating  visible  level 2 rect top=15 left=273 width=16  height=16 
New code     updating  visible  level 2 rect top=0  left=0   width=737 height=372
New code     updating  visible  level 3 rect top=0  left=0   width=737 height=372
New code     updating INVISIBLE level 4 rect top=0  left=0   width=737 height=372
New code     updating  visible  level 4 rect top=0  left=0   width=737 height=372
New code     updating  visible  level 5 rect top=0  left=0   width=722 height=372
New code NOT updating INVISIBLE level 2 rect top=15 left=273 width=0   height=0  
Invalidating invisible rect top=68 left=10  width=738 height=373
Invalidating invisible rect top=68 left=732 width=16 height=343
Invalidating invisible rect top=68 left=732 width=16 height=373
Document resource:///res/samples/test0.html loaded successfully
----

These 3 calls corresponds to (1) the entire content area, (2) the scrollbar 
without the arrows and (3) the scrollbar with the arrows.  The old page is 
repainted when we update the "level 5" widget, before these 3 calls are made.

I'll look into it again after the holydays.
If someone wants to play with it between Xmas and new year's eve...
Note: this patch contains the test code from bug 69010
This patch adds code to not do invalidates on widgets which are themselves
invisible, or which have a parent that is not visible. This avoids invalidating
widgets in a hierarchy for which paint suppression is active.

My tests show that this patch reduces page load time from 1470ms to 1280ms
(about 7%)
Ooops, patch contains some paint debugging changes too. Ignore those, and the 
whacky spacing.
a cleaner way to write this would be:

PRBool
nsWindow::ContainerHierarchyIsVisible()
{
  nsCOMPtr<nsIWidget> theParent = dont_AddRef(GetParent());
  while ( theParent ) {
    PRBool  visible;
    theParent->IsVisible(visible);
    if (!visible)
      return PR_FALSE;
 
    nsIWidget* grandparent = theParent->GetParent();
    theParent = dont_AddRef(grandparent);
  }
  return PR_TRUE;
}

right?
  

IMO, the patch works because the View manager keeps a separate list of the 
invalidates that must be done on invisible widgets (if I understood correctly 
what Kevin explained me last week).  However to make the Mac toolkit really 
behave like other platforms, we probably should mix the 2 patches above and 
accumulate invalidates on invisible widgets like in attachment #62601 [details] [diff] [review] but using 
the definition of invisible widget from attachment #65398 [details] [diff] [review].
Update on perf numbers:

Current trunk build page load:         ~1500
Trunk with patch in attachment 65398 [details] [diff] [review]:  ~1260
That's about 6%.
You're too modest: 1260/1500 = 0.84, that's a 16% (sixteen!) percent improvement.
What a beautiful day.

I'll attach a patch that does what I described in my previous comments and see if 
it doesn't spoil the fun (and if it does, I guess you can check in your patch as-
is because the View manager does the right thing for us anyhow).
Time with the second patch, plus the dcone patched fixed to account
for invisible widgets (cleaned version of attachment 62519 [details] [diff] [review]) is in the 1260 
ballpark, so that patch doesn't really help performance for the pages we're 
testing.
If I can get an sr, I've love to check this in for 0.9.8. Hyatt?
Keywords: perf
Target Milestone: --- → mozilla0.9.8
Probably obvious, but I assume you don't want to leave these uncommented:
-//#define PAINT_DEBUGGING         // flash areas as they are painted
-//#define INVALIDATE_DEBUGGING    // flash areas as they are invalidated
+#define PAINT_DEBUGGING         // flash areas as they are painted
+#define INVALIDATE_DEBUGGING    // flash areas as they are invalidated

Fix the indentation in
ContainerHierarchyIsVisible()

sr=hyatt

This patch takes pageload time from 960 to 768ms on an optimized Mach-O build 
(20%)
with hyatt's suggestion a=asa (on behalf of drivers).
Keywords: mozilla0.9.8+
Checked in.
Status: ASSIGNED → RESOLVED
Closed: 23 years ago
Resolution: --- → FIXED
Simon, your change affects only two functions:
    nsWindow::Invalidate(const nsRect &aRect, PRBool aIsSynchronous)
    nsWindow::InvalidateRegion(const nsIRegion *aRegion, PRBool aIsSynchronous)

I think you should look into these ones too to see if they are sometimes called 
when the container hierarchy is not visible:
    nsWindow::Validate()
    nsWindow::Update()
    nsWindow::HandleUpdateEvent(RgnHandle regionToValidate)
    nsWindow::UpdateWidget(nsRect& aRect, nsIRenderingContext* aContext)
    nsWindow::Scroll(PRInt32 aDx, PRInt32 aDy, nsRect *aClipRect)
    nsWindow::FindWidgetHit(Point aThePoint)
    nsWindow::Show(PRBool bState)

Note: nsWindow::Show() should still update |mVisible| if the container hierarchy 
is not visible

Otherwise I talked to Kevin again and there is no need to merge my patch that 
accumulates invalidates for invisible widgets, even for the sake of correctness.
In my build I added an assertion in UpdateWidget() if it was ever called on a 
widget with an invisible ancestor, and that was never the case. I didn't test the 
other methods you list; we could throw some assertions in for testing, I suppose.
I suggested to check the visibility of the hierarchy not as much for a question 
of performance (I guess there isn't anything else to grab beyond 16-20% :-) as 
for a question of correctness.  We are a bit too exposed to changes in the 
Compositor.  Validate() especially could be a problem, and maybe FindWidgetHit() 
too if a UI widget someday does weird things with widget visibility.

In all the above functions, we should really be using 
ContainerHierarchyIsVisible() instead of mVisible.  Some assertions would be nice 
to have indeed.  It is not worth replacing mVisible everywhere if we know that 
the function always returns true in the current code base.
Note that ContainerHierarchyIsVisible() doesn't check "this->mVisible"; it just 
starts looking at the parent. Maybe it should be changed to check mVisible too.
Agreed. A small patch for this cleanup + add some assertions would be nice. No 
need to open a new bug report.
Important note for the Mac folks....

Kevin warned me that future performance improvements with regard to the painting 
on the Mac may no longer be noticeable if we beat the paint suppression timer on 
most pages.  It would be interesting to do the same test as jrgm did in bug 34887 
comment #75 and see how many pages now beat the timer on an average machine.  It 
used to be 33 out of 40.
i would hardly call any machine hooked up to the netscape network an average
machine. Our pipeline is one of the largest and fastest in the world. 

think about modem users who will most definately _never_ beat the paint
suppression timer.
End-users don't run the page load tests.  I should have written "an average 
Netscape developer machine".  In fact, it would be even more interesting to check 
it on a super-duper Netscape developer machine because if the super-duper machine 
shows that we always beat the timer, we'll know that we can no longer rely on the 
page load tests to measure performance in painting.
I checked this with current trunk builds on win2k and Mac, running with 0ms 
page to page delay time:

"Cached" case:
  Mac OS9.1, 450MHz, 256MB, page times: avg 2.2s, max 6.0s, painted:  15 of 40
  win2k,     500MHz, 128MB, page times: avg 1.2s, max 3.5s, painted:   8 of 40

"Fully Uncached" case:
  Mac OS9.1, 450MHz, 256MB, page times: avg 2.2s, max 6.0s, painted:  17 of 40
  win2k,     500MHz, 128MB, page times: avg 1.2s, max 3.5s, painted:  11 of 40

Note: the win2k build formerly did not paint any pages. That was wrong at the 
time since some of the pages clearly take longer than the paint suppression 
threshold time to elapse. I don't know if this was specifically fixed in the 
interim, or if it is just better behaved now for some unknown reason.

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: