The SVG code caches "Covered Regions" for the frames corresponding to SVG leaf content (foreignObject, path geometry elements, and text nodes). These covered regions currently correspond to bounding box of the element's rendered pixels, stored in nsIFrame::mRect (therefore in appunits), relative to the nearest nsSVGOuterSVGFrame. They currently do not include the effects of any filters; for Gecko 1.9.1 this will be changed so that they include the effects of the filter (if any) on the leaf node, but they will still not include the effects of filters on ancestor nodes. (For example, a blur filter on an element can cause that element or its descendants to affect a larger pixel area.)
We do not store covered regions for container elements. This means every time we paint, we traverse the entire SVG frame subtree. nsSVGUtils::PaintFrameWithEffects only culls leaf frames that fail to intersect the dirty region. Currently, if a container frame has effects (filter, mask, opacity), we execute those effects even if none of its children are being drawn. Fortunately those effects should short-circuit quite well since they do take account of the dirty rect.
When PaintFrameWithEffects paints a filter for a container element, the dirty rect is expanded if necessary [XXXjwatt where?] when we paint the container element to generate the source image, to account for the fact that the filter may require a larger chunk of the source image than the dirty rect. Thus when PaintFrameWithEffects needs to check whether to draw leaf content, it can just test whether the leaf's covered region intersects the dirty rect, unless the leaf has a filter applied to it in which case PaintFrameWithEffects computes the leaf's filter bounding box instead. When we change the covered region to include leaf filter effects, the latter exception can be removed.
When we need to invalidate an SVG element, we normally just ask the nsSVGOuterSVGFrame to invalidate its covered region. But we need to adjust the region to account for filter effects on the element and its ancestors; this is done using nsSVGUtils::FindFilterInvalidation, which propagates the dirty rect up through the ancestors, adjusting the rect as we go for each ancestor with a filter. When we change the covered region to include leaf filter effects, FindFilterInvalidation will need to skip adjust for the filter on the frame that's being invalidated.
We don't cache covered regions on all elements.
TODO: Document exactly why not in convincing detail.
Proposal: would lazy caching help? During painting we would calculate and store covered regions, and
Draft GetCoveredRegion documentation
[This is very rough draft text being worked on by jwatt. You're probably wasting your time to read it in it's current state.]
In the SVG code the term "covered region" has a very specific meaning, but unfortunately this term doesn't convey the meaning or it's detailed intricacies very well. Read on for a detailed description/discussion.
An element's covered region is a rectangular area, in application units, aligned with and in the coordinate system established by the viewport that's established by the element's nearest outer 'svg' element. This "covered region" is used to calculate (but is not necessarily itself) the tight bounds of the "actual" area that would be affected by painting the element to a surface in this coordinate system. An element's covered region takes into account fill, stroke and markers, but it only partially includes the effect of filters. More specifically, it only includes the effect of filters on the element and filters on the element's descendants - it does not take into account filters on any of the element's ancestors. As a result, a filter effect (for example a gaussian blur filter) on an ancestor element could cause the area affected by an element to be bigger than the element's "covered region". Covered regions also do not take account of clipping and masking. (In fact, even when we use covered regions to find the tight bounds of the "actual" area that would be affected by painting the element, we still ignore clipping and masking. This is because, unlike filters, clipping and masking can only decrease this area, and it's simply too expensive to take them into account. The only time we take account of clipping and masking is during actual paint operations.) XXX actually when we say that covered regions take account of markers, they actually take account of the covered regions of the marks, and those covered regions are subject to the same exclusions of filters, clipping, etc.
Currently we completely ignore fill, stroke and group opacity when calculating covered regions, and we also don't take them into account when invalidating areas (so we'll do invalidation when moving a fully transparent object). If we changed covered regions to take account of transparency, then we'd need to have a mechanism to update the covered regions when opacity changes.
Covered regions have two purposes:
- To limit the area that is repainted when something changes in the document. Without them we'd need to repaint the entire window whenever something changed, and that would often be excessively expensive (slow).
- They are used internally as part of the calculations for getClientRects() and getBoundingClientRect() methods (very different to getBBox).
Some implementation details and rational:
We only cache covered regions on leaf frames (path geometry elements, text nodes, and foreignObject), where we store the value in nsIFrame::mRect. We do not cache covered regions for container elements.
One of the implications of the fact that we don't cache covered regions for container frames is that we must(?) traverse the entire SVG frame subtree when we paint, and as a result if a container frame has effects (filter, mask, opacity), we execute those effects even if none of its children are being drawn. ( nsSVGUtils::PaintFrameWithEffects only culls leaf frames that fail to intersect the dirty region.) Fortunately those effects should short-circuit quite well since they do take account of the dirty rect.
One disadvantage of maintaining covered regions in the coordinate space of the nearest outer 'svg' is that any transform changes on a parent require the covered regions of all children to be recalculated. Since getting cairo to compute fill and stroke extents is not cheap, this can really hurt. Especially think of zooming - everything has to be updated. :-(
XXX Why does nsSVGPathGeometryFrame::UpdateCoveredRegion add in markers if nsSVGPathGeometryFrame::GetCoveredRegion reevaluates and adds in the marks every time it's called?
Alternatives to the term "covered region".
- Canvas bounds
- Incomplete bounds
- Rough bounds
Whatever name is used, it would probably be a good idea to add the word "Canvas" in there to distinguish between BBox bounds and canvas bounds. Really they are partially computed canvas bounds, so maybe Get/Update/InvalidatePCCBounds would be best? It would certainly stop people jumping to the wrong conclusions about what these functions do, and would hopefully be easy te accurately remember what these functions do for those that have encountered them before and read their documenting comments.
Or perhaps just GetInvalidationRect? Since that's what we're using it for. Well, ignoring getBoundingClientRect.
UpdateCoveredRegion(PRBool aInvalidateCanvas = PR_TRUE);
So the problem with storing covered regions in mRect for containers is that we don't want the expense of recalculating them, including the effect of filters, whenever things change. It's to expensive. However. What if we got PaintSVG() to "clear" mRect for containers once painting was done? And GetCoveredRegion could cache them when it's fetched during painting. Then it doesn't need to be fetched multiple times, and we don't have the down sides of having to keep them up to date when things change.
Authors will usually filter containers, not leafs.
Using covered regions for getBoundingClientRect
Covered regions have to take filters into account to be useful as a means of invalidation. Note that doing so may reduce the area of the element though, and I'm not sure that's desirable for getBoundingClientRect. If we take into account clipping due to filter regions, it would seem inconsistent not to take real clipping, and perhaps masking, into account. And what about opacity?