Until now, every paint phase of every PaintableBox injected its own
clipping sequence into the display list:
```
before_paint: Save
AddClipRect (1)
...clip rectangles for each containing block with clip...
AddClipRect (N)
paint: ...paint phase items...
after_paint: Restore
```
Because we ran that sequence for every phase of every box, Skia had to
rebuild clip stack `paint_phases * paintable_boxes` times. Worse,
usually most paint phases contribute no visible drawing at all, yet we
still had to emit clipping items because `before_paint()` has no way to
know that in advance.
This change takes a different approach:
- Clip information is now attached as metadata `ClipFrame` to each
DisplayList item.
- `DisplayListPlayer` groups consecutive commands that share a
`ClipFrame`, applying the clip once at the start of the group and
restoring it once at the end.
Going from 10 ms to 5 ms in rasterization on Discord might not sound
like much, but keep in mind that for 60fps we have 16 ms per frame and
there is a lot more work besides display list rasterization we do in
each frame.
* https://discord.com/channels/1247070541085671459/1247090064480014443
- DisplayList items: 81844 -> 3671
- rasterize time: 10 ms -> 5 ms
- record time: 5 ms -> 3 ms
* https://github.com/LadybirdBrowser/ladybird
- DisplayList items: 7902 -> 1176
- rasterize time: 4 ms -> 4 ms
- record time: 3 ms -> 2 ms
It's useful to have tests that dump display list items, so we can more
easily see how changes to the display list recording process affect the
output. Even the small sample test added in this commit shows that we
currently record an unnecessary AddClipRect item for empty paint phases.
For now, the dump doesn't include every single property of an item, but
we can shape it to include more useful information as we iterate on it.
With this change we save a copy of of scroll state at the time of
recording a display list, instead of actual ScrollState pointer that
could be modifed by the main thread while display list is beings
rasterized on the rendering thread, which leads to a frame painted with
inconsistent scroll state.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/4288
The Skia Ganesh backend we currently use doesn't support painting from
multiple threads, which could happen before this change when the main
thread used Skia to paint on the HTML canvas while the rendering thread
was working on display list rasterization.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/4172
Deleteing set_surface() makes DisplayListPlayer API a bit more intuitive
because now caller doesn't have to think whether it's necessary to
restore previous surface after execution, instead DisplayListPlayer
takes care of it by maintaining a stack of surfaces.
The display list is an immutable data structure, so once it's created,
rasterization can be moved to a separate thread. This allows more room
for performing other tasks between processing HTML rendering tasks.
This change makes PaintingSurface, ImmutableBitmap, and GlyphRun atomic
ref-counted, as they are shared between the main and rendering threads
by being included in the display list.
This adds a command for saving the current layer of the canvas.
This is useful for painting content onto a blank background in
isolation and later compositing it onto the canvas.