In the last sections the Render Tree and Layer Tree concepts were introduced and briefly discussed. It was illustrated how painting a document boils down to painting its root layer, which then propagates the paint request down the Layer Tree.
Let’s enlighten the layer based painting approach by tracing the code in WebCore.
The main method used to paint layers is RenderLayer::paint()
which sets up the initial LayerPaintingInfo
, a data structure holding information such
as the current painting root layer. Then paintLayer()
is called, which will forward the call to paintLayerWithEffects()
, for non-composited elements, that we’ll focus on here.
paintLayerWithEffects()
will terminate the paint request if the associated renderer is part of the “normal flow”. In that case the layer is considered
as not “self-painting” and thus the renderer is responsible for painting, not the layer. If the layer is self-painting the paint request is either forwarded to
paintLayerByApplyingTransform()
or paintLayerContentsAndReflection()
- depending on if the layer is transformed or not.
Let’s discuss the code in paintLayerByApplyingTransform()
, which was stripped down for brevity:
void RenderLayer::paintLayerByApplyingTransform(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags, const LayoutSize& translationOffset)
{
...
LayoutSize offsetFromParent = offsetFromAncestor(paintingInfo.rootLayer);
TransformationMatrix transform(renderableTransform(paintingInfo.paintBehavior));
transform.translateRight(offsetFromParent.width(), offsetFromParent.height());
auto oldTransform = context.getCTM();
auto affineTransform = transform.toAffineTransform();
context.concatCTM(affineTransform);
// Now do a paint with the root layer shifted to be us.
LayerPaintingInfo transformedPaintingInfo(paintingInfo);
transformedPaintingInfo.rootLayer = this;
...
paintLayerContentsAndReflection(context, transformedPaintingInfo, paintFlags);
...
context.setCTM(oldTransform);
}
The current transformation matrix, given by the CSS transform related properties, is obtained via renderableTransform()
.
That transformation matrix is then right-translated by the offsetFromParent, the accumulated offset from the nearest transformed ancestor
(see offsetFromAncestor()
). The “right-translation” operation is equivalent to first translating to offsetFromParent, then post-multiplying to the CSS transform matrix.
The current CTM is remembered and the new transformation matrix is post-multiplied with the current CTM to form the new local user coordinate system for the layer and its descendants.
Now the layer can be painted using the same code path that’s taken, when no transformation is applied: paintLayerContentsAndReflection()
. The difference
to the code path taken if no transformations are applied is that the rootLayer in the LayerPaintingInfo
data structure has changed. It now points to
this, since the current layer is now the nearest transformed ancestor. This is relevant, since all offsets in Layer Tree during painting are computed
using offsetFromAncestor()
with respect to the rootLayer in the LayerPaintingInfo
data structure. If rootLayer is equal to this
the returned offset is zero. We’ll come back to this topic later, but let’s continue to further trace the painting code in WebCore.
Both transformed and regular boxes end up at paintLayerContentsAndReflection()
. This method handles CSS reflections (we’re not going to discuss this)
and then calls paintLayerContents()
.
paintLayerContents()
is responsible to set up clipping paths, filters, opacity, etc. After the setup of these effects, it utilizes RenderBox::paint()
to paint the box in each of the CSS paint phases. In this design the renderers can be kept simple, since all complex graphical effects are handled in one place, RenderLayer.
Let’s stop the discussion for HTML/CSS and summarize: HTML/CSS handle transformations / clip-paths / filters / opacity etc. through RenderLayer. We didn’t explicitly look this up, but take my word on this: all logic related to hardware acceleration is based upon RenderLayer.
So how does SVG painting work in WebKit at present?