Gecko:OffMainThreadPainting

From MozillaWiki
Jump to: navigation, search

Problem

Painting is slow. On complex, dynamic pages, we spend a large amount of CPU time rasterizing the page which blocks the main thread from being responsive. Ongoing work to reduce the amount of painting done (DLBI), and improve the speed of painting are helping with this, but being able to offload the entirety of this work to another thread will be a significant gain.

Also, on platforms where we have asynchronous scrolling (Android and B2G currently), the scrolling is limited to within a prerendered viewport area. When scrolling goes outside of this area, the main-thread is required to draw the new content. If this takes too long, we are forced to checkerboard until the content is available. Being able to render newly scrolled in content, without accessing the main thread, would reduce the chance of users seeing checkerboarding.

Goals

  • Improve painting throughput performance (increase frames per second drawn to the screen)
  • Reduce the load on the main thread to improve responsiveness and reduce jank.
  • Reduce redraw latency when scrolling

Proposed solution

Modify our current display item list items to be entirely self contained, such that they only access main-thread data during construction (or an explicit finalization pass). We can then pass the entire tree across to the painting thread to do all visibility analysis, layer building and ThebesLayer painting. This would require that ownership and access to FrameLayerBuilder and Layers become painting-thread only, instead of main-thread only as they currently. Compositing then happens on yet another thread via off-main-thread compositing (OMTC - Bug 598873).

The painting thread could use worker threads for the actual rasterization pass, so that we can have multiple paints being done in parallel.

We can also start retaining the display item list on the painting thread. This, once it is integrated with async scrolling (APZC) would let us rasterize new content during content without accessing the main-thread. We could build a layer tree for the entire page, but only rasterize content that falls within the prerendered viewport. When async scrolling attempts to scroll outside of the prerendered area, we would notify the painting thread of the new desired prerendered area and rasterize it.

Alternatives

Chrome's proposed solution to the same problem (https://sites.google.com/a/chromium.org/dev/developers/design-documents/impl-side-painting) is lower level. Rather than using high-level drawable objects (display items), they plan to serialize a stream of skia drawing commands. This lets them store the entirety of a layer's content (within reason), not just restricted to areas that are currently visible. They can then rasterize newly exposed content during scrolling without accessing the main thread, and without the large memory trade-off of pre-rendering areas that are expected to be scrolled into view. They can also dynamically re-rasterize areas of layers at a new resolution or quality setting without requiring main thread access.

Plan

  • Build infrastructure to run a painting thread, and ship display items across to it.
  • Fix a few basic DisplayItem types to not require main-thread access, such that we can run basic reftest style pages off main-thread.
    • Ideally it would be nice to have dynamic fallback to main-thread painting if other display item types are encountered, but not necessary.
  • Start porting the remainder of the display item types to not require main-thread access
    • Each display item should be a fairly self contained chunk of work, so we can parallelize this stage well
  • Add display item caching for fully asynchronous scrolling
  • Investigate having multiple rendering threads

Prerequisites

  • OMTC finished and enabled on all platforms
  • It would be worthwhile to wait for the Layers refactoring to be completed to avoid too many conflicts.

Details and Specifics

Need to find all external users of Layers and FrameLayerBuilder and find a way to avoid direct access.

  • Images/ImageContainer
  • Canvas
  • GetDedicatedLayerFor
  • InvalidateAllLayers
  • DLBI cached display item data
  •  ?

Can we prevent accidental access of main-thread data from the painting thread? Both during the transition process, and for new code added in the future.

  • We can pepper nsIFrame methods with IsMainThread() assertions, this would hopefully catch a large proportion of potential issues.
  •  ?

We need to investigate throttling/scheduling for the main-thread/painting-thread so that they don't end up too out of sync.

Invalidation (and thus MozAfterPaint regions) will now be happening on the painting-thread, so we need to pass that back to fire callbacks.

Testing

I don't imagine us needing anything specific with regards to testing. Since this is an intermediate stage between layout and compositing, existing reftests (that are testing OMTC) should also be fine for testing correctness. Our existing performance testing infrastructure shouldn't be affected. It's possible that page-load times may regress because of the added overhead of changing threads.

Display Item Conversion

Backgrounds - nsCSSRendering

  • A decent block of code, but should be fairly easy apart from images.

Images

  • Imagelib only supports main-thread access currently, need to find a way to ship the images (preferably without blocking on decoding) to the painting thread.

Text

  • Unknown

Style Data

  • Can we allow access to style structs from the painting thread?

SVG Images

-moz-element