Gecko:Compositor
Contents
Goals
- Smooth screen updates in the presence of scroll operations, fixed positioning (and related content) and windowed plugins
- Efficient invalidation calls
- Quick repaints in response to input
- Integrate with animation facilities (SMIL etc)
- Good synchronization of video with audio or other timed events
- Limit updates to some frame rate
- Make updates always consistent with the current state of the content
Current State
Tracked in bug 374980 for possible inclusion in platform for 1.9.2. Also see Roc's July 2009 blog post and mozilla.dev.platform post about compositor phases.
2007 State
View managers support update batching. This doesn't work too well; it interacts badly with requests for "immediate update", and batching is per document, not per window. It also doesn't meet the above goals for animation, or smooth updates when scrolling (especially with fixed-pos content and the like), or frame rates. We also have problems with reentrancy when painting; we try to flush reflows to get consistent updates, but this can cause immediate updates or other things that cause reentrant painting... it's fragile.
We also have very fragile handling of paint event priority vs other events. This is platform-specific.
Proposal
- Have just one native widget per (top level) window: Native widget overhaul
- Rip out all the view update batching.
- Introduce a compositor manager to handle invalidation requests. It should be attached to the root nsPresContext. It will need to manage invalidation for the top level window and all popups.
- Rip out existing event-based scheduling of restyles and reflows, because they will be scheduled by the compositor manager instead.
Compositor Manager
The compositor manager offers the following APIs to layout:
- RequestUpdate(callback). Request eventual update of content in the window. This can be used to schedule reflows and restyles.
- Invalidate(nsRect). Request eventual repaint of an area of the window.
- RequestUrgentUpdate(). Request "ASAP" update of the window (but still asynchronous).
- Scroll(nsRect, nsPoint delta). Request that an area of the window be scrolled via bitblit.
The compositor tracks the invalid area of the window. A Scroll() request updates the invalid area (e.g. by "scrollling" the invalid area that intersects the scroll area) even though the scrolling doesn't happen synchronously.
RequestUrgentUpdate() should be called whenever we need to update in response to user input.
Periodically the compositor updates the window. Window update takes the following steps:
- Estimate the time at which the frame will be drawn (e.g., add the time required by the last update to the current time). Call this time T.
- Ask all active animation subsystems to update their animations to time T.
- Flush all restyles and reflows (and anything else registered with RequestUpdate).
- Compute positions of all plugins.
- Compute the area that will need to be painted after all Scroll() requests have been executed, all plugins have been moved, and all invalid areas repainted.
- Render that area's content to an offscreen buffer.
- Perform all Scroll() requests.
- Move all plugins.
- Copy contents of offscreen buffer to the window.
As an optimization, if the platform has built-in double buffering support and there are no pending Scroll() or plugin movements, then we can render the invalid area directly to the window.
At some point we should probably make all JS timeouts scheduled for up to time T run before the window update.
Scheduling Updates
Let the desired frame rate be H Hz. Let the timestamp of the start of the last update be S (initially 0). Let the current timestamp be Now(). There is always at most one pending update. Updates are scheduled as follows:
- RequestUrgentUpdate() sets a flag mImmediateUpdate to true. It schedules an update for Now() (cancelling any later scheduled update).
- On an RequestUrgentUpdate(), Invalidate() or Scroll() call, if there is currently no update scheduled, schedule one for max(Now(), S + 1/H). (I.e., make the update happen as soon as possible, except that we want it to render no earlier than 1/H seconds since the last update) --- except that if mImmediateUpdate is true, then always schedule an update for Now().
- After a window update, set mImmediateUpdate to false. If there are still active animations, schedule another update for max(Now(), S + 1/H).
For hidden windows, we should never perform updates at all. Showing a window forces an immediate update.
Interaction With OS Painting
I expect that the window update will be performed via an XPCOM timer event. The last step will be platform-specific; some platforms might need to stash the buffer somewhere, force a synchronous platform-level paint, and draw out of the buffer in an event handler.
When a platform expose event is received, we add the dirty area to our invalid region and handle it as an immediate window update. This events will be increasingly rare as platforms move to compositing window managers. This approach may cause problems if the platform handles scrolling and widget movement badly during an expose event; if so, then an unanticipated platform expose event with plugin movement or scrolling pending will need to be handled by just adding the exposed area to the invalid region and calling RequestUrgentUpdate() to schedule a window update.
Prioritization
I think that the frame rate limiter will take care of most paint prioritization problems. It ensures that we will not paint too much while there is no user input, but we still paint immediately whenever there is user input, for best responsiveness.
By replacing the event-based scheduling of restyles and reflows, the frame rate limiter will suppress/coalesce restyles and reflows too.
Smooth Scrolling
We can rework smooth scrolling to treat it as an animation source in the compositor framework. This will simplify it and make smooth-scrolling much more visually consistent, smooth, and performant in the face of concurrent invalidations.
We can also dispatch onscroll DOM events from the compositor as a RequestUpdate callback. This guarantees that onscroll will be called in a timely AND safe manner.
Unresolved Issues
We need to be able to selectively flush changes to certain documents without flushing all parents or children. Otherwise chrome performance will be gated by flushes to dynamic (e.g. animated) content and even execution of content scripts such as onscroll handlers.