Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve clipping and positioning documentation #2892

Merged
merged 1 commit into from Jul 17, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Improve clipping and positioning documentation

The new documentation gives a more complete and up-to-date picture of
WebRender's clipping and positioning architecture.
  • Loading branch information
mrobinson committed Jul 16, 2018
commit 8ad0431b423140eb73485dfd9d6a68f6e133c062

This file was deleted.

@@ -0,0 +1,167 @@
# Original Design

To understand the current design for clipping and positioning (transformations
and scrolling) in WebRender it can be useful to have a little background about
the original design for these features. The most important thing to remember is
that originally clipping, scrolling regions, and transformations were
properties of stacking contexts and they were completely _hierarchical_. This
goes a long way toward representing the majority of CSS content on the web, but
fails when dealing with important edges cases and features including:
1. Support for sticky positioned content
2. Scrolling areas that include content that is ordered both above and below
intersecting content from outside the scroll area.
3. Items in the same scrolling root, clipped by different clips one or more of
which are defined outside the scrolling root itself.
4. Completely non-hierarchical clipping situations, such as when items are
clipped by some clips in the hierarchy, but not others.

Design changes have been a step by step path from the original design to one
that can handle all CSS content.

# Current Design

All positioning and clipping is handled by the `ClipScrollTree`. The name is a
holdover from when this tree was a tree of `Layers` which handled both
positioning and clipping. Currently the `ClipScrollTree` holds:
1. A hierarchical collection of `SpatialNodes`, with the final screen
transformation of each node depending on the relative transformation of the
node combined with the transformations of all of its ancestors. These nodes
are responsible for positioning display list items and clips.
2. A collection of `ClipNodes` which specify a rectangular clip and, optionally,
a set of rounded rectangle clips and a masking image.
3. A collection of `ClipChains`. Each `ClipChain` is a list of `ClipNode`
elements. Every display list item has an assigned `ClipChain` which
specifies what `ClipNodes` are applied to that item.

The `SpatialNode` of each clip applied to an item is completely independent of
the `SpatialNode` applied to the item itself.

One holdover from the previous design is that both `ClipNode` and `SpatialNodes`
have a parent node, which is either a `SpatialNode` or a `ClipNode`. From this
node WebRender can determine both a parent `ClipNode` and a parent `SpatialNode`

This comment has been minimized.

@kvark

kvark Jul 13, 2018

Member

This is confusing me quite a bit. Why do we want to support the ClipNode being a parent, ever?

This comment has been minimized.

@mrobinson

mrobinson Jul 16, 2018

Author Member

Nowadays there is no reason this has to be the case, but consider that in the past the only way to combine clip nodes was to have them be part of the ancestry in the ClipScrollTree. With the ClipChain API and the fact that an item can have both a positioning node and clip node assigned, it isn't strictly necessary. Once both Gecko and Servo are using the ClipChain API for all clips and never using ClipNodes as positioning nodes, we can fix this for good.

This comment has been minimized.

@kvark

kvark Jul 16, 2018

Member

Thank you for explanation! @staktrace do you know if there is an issue to file/track for this on Gecko side?

This comment has been minimized.

@staktrace

staktrace Jul 16, 2018

Contributor

@kvark I don't believe there is; please feel free to file one

This comment has been minimized.

by finding the first ancestor of that type. This is handled by the
`DisplayListFlattener`.

## `SpatialNode`
There are three types of `SpatialNodes`:
1. Reference frames which are created when content needs to apply
transformation or perspective properties to display list items. Reference
frames establish a new coordinate system, so internally all coordinates on
display list items are relative to the reference frame origin. Later
any non-reference frame positioning nodes that display list items belong
to can adjust this position relative to the reference frame origin.
2. Scrolling nodes are used to define scrolling areas. These nodes have scroll
offsets which are a 2D translation relative to ancestor nodes and, ultimately,
the reference frame origin.
3. Sticky frames are responsible for implementing position:sticky behavior.
This is also an 2D translation.

`SpatialNodes` are defined as items in the display list. After display list
"flattening" each node is traversed hierarchically during the
`ClipScrollTree::update()` step. Once reference frame transforms and relative
offsets are calculated, a to screen space transformation can be calculated for
each `SpatialNode`. This transformation is added the `TransformPalette` and
becomes directly available to WebRender shaders.

In addition to screen space transformation calculation, the `SpatialNode` tree
is divided up into _compatible coordinate systems_. These are coordinate systems
which differ only by 2D translations from their parent system. These compatible
coordinate systems may even cross reference frame boundaries. The goal here is
to allow the application clipping rectangles from different compatible
coordinate systems without generating mask images.

## `ClipNode`

Each clip node holds a clip rectangle along with an optional collection of
rounded clip rectangles and a mask image. The fact that `ClipNodes` all have a
clip rectangle is important because it means that all content clipped by a
clip node has a bounding rectangle, which can be converted into a bounding
screen space rectangle. This rectangle is called the _outer rectangle_ of the
clip. `ClipNodes` may also have an _inner rectangle_, which is an area within
the boundaries of the _outer rectangle_ that is completely unclipped.

These rectangles are calculated during the `ClipScrollTree::update()` phase. In
addition, each `ClipNode` produces a template `ClipChainNode` used to build
the `ClipChains` which use that node.

## `ClipChains`

There are two ways that `ClipChains` are defined in WebRender. The first is
through using the API for manually specifying `ClipChains` via a parent
`ClipChain` and a list of `ClipNodes`. The second is through the hierarchy of a
`ClipNode` established by its parent node. Every `ClipNode` has a chain of
ancestor `SpatialNodes` and `ClipNodes`. The creation of a `ClipNode`
automatically defines a `ClipChain` for this hierarchy. This behavior is a
compatibility feature with the old completely hierarchical clipping architecture
and is still how Gecko and Servo create most of their `ClipChains`. These
hierarchical `ClipChains` are constructed during the `ClipNode::update()` step.

During `ClipChain` construction, WebRender tries to eliminate clips that will
not affect rendering, by looking at the combined _outer rectangle_ and _inner
rectangle_ of a `ClipChain` and the _outer rectangle_ and _inner rectangle_ of
any `ClipNode` appended to the chain. An example of the goal of this process is
to avoid having to render a mask for a large rounded rectangle when the rest of
the clip chain constrains the content to an area completely inside that
rectangle. Avoiding mask rasterization in this case and others has large
performance impacts on WebRender.

# Clipping and Positioning in the Display List

Each non-structural WebRender display list item has
* A `ClipId` of a `SpatialNode` or `ClipNode` for positioning

This comment has been minimized.

@kvark

kvark Jul 13, 2018

Member

a clip id of a clip node for positioning - this rings a bell for me

* A `ClipId` of a `ClipNode` or a `ClipChain` for clipping
* An item-specific rectangular clip rectangle

The positioning node determines how that item is positioned. It's assumed that
the positioning node and the item are children of the same reference frame. The
clipping node determines how that item is clipped. This should be fully
independent of how the node is positioned and items can be clipped by any
`ClipChain` regardless of the reference frame of their member clips. Finally,
the item-specific clipping rectangle is applied directly to the item and should
never result in the creation of a clip mask itself.

Perhaps the most inconvenient holdover from the previous single-tree
hierarchical design is that `SpatialNodes`, `ClipNodes`, and `ClipChains` all
share a single `ClipId` id type. This means that the client must be a bit
careful when using the API. For instance, when specifying the parent of
`ClipNode` one can use the `ClipId` or another `ClipNode` or a `SpatialNode`,
but not one for a `ClipChain`.

WebRender's internal representation of clipping and positioning is not a perfect
match to the display list representation of these concepts. This is due, again,
to the evolutionary nature of the design. The general trend is that the display
list gradually moves toward the internal representation. The most important of
these incongruities is that while `ClipNodes`, sticky frames, and scroll frames
are defined and simply return a `ClipId`, reference frames return a `ClipId` and
also are pushed and popped like stacking contexts.

## Converting `ClipId` to global `ClipScrollTree` indices

WebRender must access `ClipNodes` and `SpatialNodes` quite a bit when building
scenes and frames, so it tries to convert `ClipIds`, which are already
per-pipeline indices, to global scene-wide indices. Internally this is a
conversion from `ClipId` into `SpatialNodeIndex`, `ClipNodeIndex` or
`ClipChainIndex`. In order to make this conversion cheaper, the
`DisplayListFlattner` assigns offsets for each pipeline and node type in the
scene-wide `ClipScrollTree`.

Nodes are added to their respective arrays sequentially as the display list is
processed during "flattening." When encountering an iframe, the

This comment has been minimized.

@kvark

kvark Jul 13, 2018

Member

let's not include the "." in quote marks

This comment has been minimized.

@mrobinson

mrobinson Jul 16, 2018

Author Member

So weirdly this is the style in American English, which I admit is bizarre. Since the rest of the document is written in American English though, maybe it makes sense to be consistent here.

`DisplayListFlattener` must start processing the nodes for that iframe's
pipeline, meaning that nodes are now being added out of order to the node arrays
of the `ClipScrollTree`. In this case, the `ClipScrollTree` fills in the gaps in
the node arrays with placeholder nodes.

# Hit Testing

Hit testing is the responsibility of the `HitTester` data structure. This
structure copies information necessary for hit testing from the
`ClipScrollTree`. This is done so that hit testing can still take place while a
new `ClipScrollTree` is under construction.

# Ideas for the Future
1. Expose the difference between ids for `SpatialNodes`, `ClipNodes`, and
`ClipChains` in the API.
2. Prevent having to duplicate the `ClipScrollTree` for hit testing.
3. Avoid having to create placeholder nodes in the `ClipScrollTree` while
processing iframes.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.