Update on what happened in WebKit in the week from July 14 to July 27.
This two-week update includes plenty of changes to the Skia compositor, changes to multimedia support, three blog posts, and assorted improvements.
Cross-Port 🐱
The Web Inspector “Layout & Rendering” timeline now shows a Layout Invalidated event for every element
that needs relayout, not just the layout root (with the old root-only event
renamed to Layout Scheduled). This unveils why some layouts take much longer
than others. No more guessing which of dozens of nodes is actually to blame!
The webkit://gpu page has gained a dark
style, which will be used when the
system settings indicate that dark mode is preferred by the user.
Multimedia 🎥
GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.
The experimental GstWebRTC backend was
removed and libwebrtc usage was
enabled in the main branch. We hope
to enable WebRTC support by default in the 2.56 series, scheduled around March
2027.
MP4 edit lists support was enabled in the MSE backend, improving timestamp accuracy, specially when handling of B-frames.
Graphics 🖼️
Split the compositing walk in the
Skia compositor into a damage pass and a paint pass, so the frame damage is
known before the first draw. The damage pass walks the layer tree with a
SkNoDrawCanvas in place of the real canvas, so every draw is discarded and
only the damage is collected. Both passes run from a single paint() that
applies animations and computes the transforms once, so the two see the same
tree. Knowing the damage up front is what lets the compositor eventually paint
only the parts of a frame that actually changed.
Wired up damage-driven compositing on
the Skia compositor, so a frame re-composites only the region that actually
changed instead of the whole surface, when the
UseDamagingInformationForCompositing feature is enabled (not yet on by
default). Each frame's damage is combined with what each swap-chain target
still needs to redraw since it was last drawn into, and the clear and every
draw are clipped to that region, which is a milestone towards no longer
repainting untouched pixels every frame.
Made the root layer collect the frame damage itself in the Skia compositor, instead of having each layer report its own changes. Reporting leaves a gap whenever a layer is in no position to report, e.g. a destroyed one took its painted rectangle with it, so what it had drawn stayed on screen. The root now holds one rectangle per layer and compares it against what each frame's walk finds, so a layer that moved is repainted in both places, and a layer the walk never reaches is repainted where it used to be and dropped. Nothing has to notice anything for the pixels it left behind to be repainted, which is what makes it safe to restrict composition to the damaged region by default in future commits.
Limited every content draw to the target's repaint region in the Skia compositor, so a composited frame can redraw only the pixels that actually changed. Each content type restricts itself to the region's rectangles rather than clipping the canvas, since a multi-rectangle clip cannot be a hardware scissor and would make Skia build a mask and break batching. This is the groundwork for damage-driven compositing, which stays off by default behind the damage-tracking feature flag, as the compositor still passes no region and nothing is restricted yet.
Made each swap-chain target track its own damage since it was last current. Repainting only what changed is correct only when drawing into the target that holds the previous frame, but the swap chain hands back whichever target is free, and that one is a frame or more behind. Each frame's damage is now added to every target as it is recorded and cleared from a target when that target is presented, instead of being built as a side effect of reading it.
Taught the tile and image draws in the Skia compositor to split themselves by
damage rectangle, so a frame only repaints
the parts of a layer that actually changed. A new SkiaDamageRegion holds the
frame's damage in device space and is built once per frame, and each draw is
restricted to it: skipped when it touches no damage, split into one sub-draw
per damage rectangle it overlaps, or drawn under a device-space clip when a rotated
or skewed transform rules out working with rectangles. Nothing feeds a damage region
in yet, so every draw still paints in full—this prepares for future patches
enabling using damage information in the composition
Fixed missing repaints when compositor-applied layer state changes dynamically in the Coordinated Graphics backend. A layer recorded damage when its backing store re-rendered or a new contents buffer arrived, but the compositor also handles filters, masks, clip path changes, the contents rectangle, the contents tiling, the blend mode and contents visibility, and changing any of those alters the pixels it produces without dirtying a tile. Those setters now damage the whole layer, so a compositor that repaints only the damaged rectangles no longer leaves the previous frame's pixels on screen.
Community & Events 🤝
Nikolas Zimmermann has written a two-part blog series about the current the new Layer-Based SVG Engine (LBSE), with the first post covering the effort to reduce layer overhead using layers conditionally, and the second about how compositing is being implemented and the complications introduced due to paint ordering rules.
Loïc Le Page has published a blog post explaining how to use the new WPEPlatform API to implement a custom WPE integration. While presented example uses GLFW and EGL to show Web content on an X11 window, the concepts are useful for anyone looking into embedding WPE.
That’s all for this week!