Injustice - Developers Among Us (SciFiDevCon 2024)
Integrating the new Layer-Based SVG Engine
1. Integrating the new
Layer-Based SVG Engine
WebKit Contributors Meeting 2023
Cupertino (CA), 24-25 October 2023
Rob Buis
Nikolas Zimmermann
rbuis@igalia.com
nzimmermann@igalia.com
1
2. A short introduction
Nikolas Zimmermann, Igalian since 2019 - located in Germany
Rob Buis, Igalian since 2017 - located in Ireland
Founders of kdom, kcanvas, ksvg2/khtml2
khtml contributors since 2001
WebKit contributors since 2005
WebKit reviewers since February 2007
2
5. What is LBSE?
Layer-Based SVG Engine
Codename for new SVG engine in WebKit
Started as Proof-Of-Concept in September 2019
Goal: resolve architectural issues present since 15+ years
Developed by Igalia, funded by Igalia, Vorwerk and WIX (since Q4 2023).
WIX.com offers a Website-Builder empowering 200.000.000+ webpages
5
6. What is LBSE?
Layer-Based SVG Engine
Enable hardware-accelerated compositing / hardware-accelerated transform animations
Enable 3D transform support for arbitrary SVG content, unlock perspective transformations
Unify HTML/SVG rendering pipelines, which are mutually exclusive at present
Proof-Of-Concept patch passed all existing layout tests in October 2021 (1)
Performance comparable to legacy engine in e.g. MotionMark (2)
(1) Compositing/z-index/will-change/... was only tested in most basic scenarios for SVG.
(2) MotionMark suffers from layer overhead ⟶ LBSE ~2-4% slower.
6
7. How is it achieved?
Let SVG participate in the layer tree mechanism.
Remove knowledge about transform handling out of the SVG renderers.
Remove SVG specific clipping/marker/masking/filter code
Redesign SVG render tree to be convenient for the existing CSS code.
(Coordinate system decisions, etc.)
Reuse as much as code possible in RenderLayer - without SVG specific changes.
If painting is handled via RenderLayer, features such as compositing, 3D transformations, z-index, etc. work
just fine for SVG, as side-effect.
7
8. Evolution since 2021
The "final" version of the prototype was a drop-in replacement for the old SVG engine.
During and after the WebKit contributors meeting 2021, a plan was established how LBSE can be integrated
into WebKit, without violating the usual high standards with respect to reviewability.
As a consequence...
No way to use any existing patch as-is from LBSE downstream
Lots of manual work necessary.
It is equal to yet another rewrite.
8
9. Upstreaming plan
It was decided to bring-up LBSE in small, reviewable atomic pieces in parallel
to the legacy SVG engine, share code where it makes sense, and split elsewhere.
All the code is behind a compile-time flag ENABLE(LAYER_BASED_SVG_ENGINE)
and an additional setting LayerBasedSVGEngineEnabled that can be
used to toggle between LBSE and the legacy SVG engine at runtime.
⟶ Upstreaming started Dec 2021
9
12. Progress tracking
The bug report tracks the upstreaming
status in WebKit Bugzilla; individual commits are tracked on .
110 patches so far directly related with LBSE
(first: 29. November 2021, current: 17. October 2023).
Overall status ~ 77%
"#90738 - Harmonize HTML & SVG rendering"
"GitHub WebKitIgalia #1"
12
13. Upstreamed patches
In this presentation we will only highlight the changes to LBSE since the last WebKit contributors meeting.
Let's start with a look at the upstreamed patches since October 2022.
2022-11-09 - [LBSE] Add remaining compositing tests from LBSE downstream
2022-11-09 - [LBSE] Add transform related tests from LBSE downstream
2022-11-09 - [LBSE] Enforce presence of a RenderSVGViewportContainer, even if the <svg> has no
children
2022-11-09 - [LBSE] Allow to query supplementalTransform() without creation
2022-11-09 - [LBSE] Transform update handling is unreliable
13
14. 2022-11-09 - [LBSE] Unify all SVG geometry/transform computations with CSS
→ Major change: SVG layout no longer depends on transform - aligning with HTML/CSS.
2022-12-12 - Remove orphaned function definition from RenderLayerModelObject
2022-12-12 - Cleanup SVGContainerLayout::layoutSizeOfNearestViewportChanged
2022-12-12 - Fix reset to baseVal after animation end for SVGTransformList
2022-12-12 - [LBSE] Do not relayout on dynamic transform changes, if not necessary
2022-12-12 - [LBSE] Add new tests that cover repainting after transform changes
14
15. 2023-01-18 - [LBSE] Update 'mac-ventura-wk2-pixel' pixel test baseline
2023-01-18 - [LBSE] Update 'mac-ventura-wk2-lbse-text' test baseline
2023-05-30 - [LBSE] Update 'mac-ventura-wk2-pixel' test baseline
2023-06-04 - REGRESSION: Dynamic attribute updates partly broken for SVG
2023-06-14 - [LBSE] Gardening, update pixel+text results
2023-06-16 - [LBSE] Computation of on-screen font scaling factor ignores page zoom
15
16. 2023-06-16 - [LBSE] Enable accelerated transform animations for SVG renderers
→ Major change: Continuing the effort for layout-free transform changes, we now support accelerated
transform animations.
2023-09-17 - [LBSE] Gardening, update pixel+text results
2023-09-19 - Move more SVG code into legacy
2023-09-20 - Move RenderSVGResourceContainer into legacy
2023-09-24 - [LBSE] Errorous/unncessary repainting when viewBox is used on <svg> elements
16
17. 2023-10-05 - [LBSE] Fix visual overflow computation
→ Important change: Correct visual overflow, previously leading to unnecessary repaints
2023-10-07 - [LBSE] Incorrect clipping for non-opaque, outermost <svg> elements
2023-10-07 - [LBSE] Garden mac-ventura-wk2-pixel / mac-ventura-wk2-lbse-text pixel test baselines
2023-09-21 - [LBSE] Add RenderSVGResourceContainer
2023-09-21 - [LBSE] Add RenderSVGResourceClipper
→ Latest effort: Bring SVG resources (clipping first) to LBSE.
17
19. Next steps
We'll have the following tasks in LBSE in the next 6 months:
Finish upstreaming (many patches fixing individual LayoutTests still missing)
Re-design resource handling for LBSE (clip/mask/filter, etc.)
Re-design resource invalidation logic (without relying on layout)
19
20. Resource handling redesign
Resource invalidation is fundamentally broken in the legacy SVG engine. Example:
DOM tree
<defs>
<clipPath id="clip">
<circle r="10"/>
</clipPath>
<mask id="mask">
<rect x="5" clip-path="url(#clip)"/>
</mask>
</defs>
<path mask="url(#mask)"/>
Render tree
RenderSVGHiddenContainer ("defs")
RenderSVGResourceClipper ("clip")
RenderSVGEllipse ("circle", r=10)
RenderSVGResourceMasker ("mask")
RenderSVGRect ("rect", x=5) | uses "clip" resource
RenderSVGPath ("path") | uses "mask" resource
A mask is applied to a path, and a child of that mask it clipped.
20
21. Resource handling redesign
Handling invalidations? An early design
decision was to re-use the `layout()` logic
of the render tree to handle invalidations.
Consider changing the radius to 20:
1. Parse attribute in
SVGCircleElement, update
presentational style and trigger style
invalidation.
2. During style resolving
RenderSVGEllipse receives a new
style with a changed radius requiring a
re-layout, which is triggered
asynchronously.
Render tree
RenderSVGHiddenContainer ("defs")
RenderSVGResourceClipper ("clip")
RenderSVGEllipse ("circle", r=10)
RenderSVGResourceMasker ("mask")
RenderSVGRect ("rect", x=5) | uses "clip" resource
RenderSVGPath ("path") | uses "mask" resource
21
24. Resource handling redesign
Resource invalidation is fundamentally broken in the legacy SVG engine. Example:
Handling invalidations?
An early design decision was to re-use
the layout() logic of the render tree to
handle invalidations. Consider changing
the radius to 20. What happens?
3. The resources in the ancestor chain are
notified about the style change, clients
of resources are invalidated.
RenderSVGRect is marked for layout.
4. After style resolving finished, a re-
layout is triggered asynchronously.
Render tree
RenderSVGHiddenContainer ("defs")
RenderSVGResourceClipper ("clip")
RenderSVGEllipse ("circle", r=10)
RenderSVGResourceMasker ("mask")
RenderSVGRect ("rect", x=5) | uses "clip" resource
RenderSVGPath ("path") | uses "mask" resource
24
25. Resource handling redesign
Relevant call graphs:
3. RenderSVGRect is marked for layout (as it uses mask, which uses clip, which got modified)
frame #0: `WebCore::RenderObject::setNeedsLayout(this=0x0000000164005980, markParents=MarkContainingBl
frame #1: `WebCore::RenderSVGResource::markForLayoutAndParentResourceInvalidation(object=0x00000001640
frame #2: `WebCore::LegacyRenderSVGResourceContainer::markAllClientsForInvalidation(this=0x00000001640
frame #3: `WebCore::LegacyRenderSVGResourceClipper::removeAllClientsFromCache(this=0x0000000164005520
frame #4: `WebCore::RenderSVGResource::markForLayoutAndParentResourceInvalidation(object=0x00000001640
frame #5: `WebCore::SVGResourcesCache::clientStyleChanged(renderer=0x00000001640056a0, diff=Layout, ol
frame #6: `WebCore::LegacyRenderSVGModelObject::styleDidChange(this=0x00000001640056a0, diff=Layout, o
frame #7: `WebCore::RenderElement::setStyle(this=0x00000001640056a0, ...)
frame #8: `WebCore::RenderTreeUpdater::updateRendererStyle(this=0x000000016f374b50, ...)
...
frame #14: `WebCore::Document::updateStyleIfNeeded(this=0x0000000116145a00)
25
26. Resource handling redesign
It's a fundamental flaw that we need to get rid of: calling setNeedsLayout from within layout is evil.
Furthermore the correctness of the invalidation chain depends on the element order in DOM.
This is known since a long time, dating back to at least 11 years ago:
...
→ Rewrite resource handling from scratch for LBSE, avoiding the need for layout() for resource
invalidation.
Bug #81515 - SVG Resources layout needs refactoring
Bug #179788 - Make RenderSVGResource::markForLayoutAndParentResourceInvalidation() more robust
Bug #207451 - RenderSVGShape invalidates all its resources when it needs layout, but is that necessary?
Bug #208903 - [SVG] RenderSVGResourceContainer's style invalidation should be a pre-layout task
Bug #242420 - ☂️avoid invalidating SVG resources when referencing element changes layout
26
27. Resource handling redesign - plan
LBSE will implement masking/clipping in a stateless way
→ No more temporary ImageBuffers and per-RenderObject caches
Implementation will start with RenderSVGResourceContainer (new base class)
→ Already landed in Oct 2023.
Next target: RenderSVGResourceClipper
→ First basic implementation landed last Sunday.
Ongoing: Rewrite of resource invalidation logic
27
28. Resource handling redesign - plan
Move SVGResourcesCache to LegacySVGResourcesCache.
Revisit the whole code related to SVG resources, and rewrite it to avoid using layout() to communicate
changes -- repaint instead of relayout where possible.
→ This will cure a 15+ year old wrong design decision and bring performance improvements!
28
29. Mid-term plans
Finish support for <clipPath>. Already solved in LBSE downstream without requiring any temporary
ImageBuffer objects nor any cached state: Porter-Duff compositing solves the problem.
Add support for <mask>: trivial after clipping.
Add support for <linearGradient> / <radialGradient> (needs LBSE aware
RenderSVGResourceContainer).
Add support for <pattern> / <marker> - the last two missing SVG features.
29
30. Long-term plans
Finish LBSE implementation, such that all layout tests pass.
Finish performance work to make sure LBSE is not slower in any standard benchmark.
Reduce RenderLayer overhead.
Cache more information about the SVG subtree at each container parent, to be able to skip certain tree
walks in RenderLayer for whole sub-trees if possible.
Selectively construct RenderLayer's only if necessary.
... (there are more ideas in the pipe, that need testing)
Turn on LBSE by default. Remove legacy SVG engine.
2024 is the target!
30