LibWeb: Reuse display list across repaints

...if only the scroll offset is updated.

Currently, on any document with a large amount of content, the process
of building a display list is often more expensive than its
rasterization. This is because the amount of work required to build a
display list is proportional to the size of the paintable tree, whereas
rasterization only occurs for the portion visible in the viewport.

This change is the first step toward improving this process by caching
the display list across repaints when neither style nor layout requires
invalidation. This means that repainting while scrolling becomes
significantly less expensive, as we only need to reapply the scroll
offsets to the existing display list.

The performance improvement is especially visible on pages like
https://ziglang.org/documentation/master/ or
https://www.w3.org/TR/css-grid-2/
This commit is contained in:
Aliaksandr Kalenik 2024-08-12 00:49:32 +02:00 committed by Andreas Kling
commit 18fc23b3d6
Notes: github-actions[bot] 2024-08-19 16:58:08 +00:00
6 changed files with 46 additions and 1 deletions

View file

@ -380,9 +380,11 @@ Document::Document(JS::Realm& realm, const URL::URL& url, TemporaryDocumentForFr
if (!node)
return;
if (auto navigable = this->navigable(); !navigable || !navigable->is_focused())
auto navigable = this->navigable();
if (!navigable || !navigable->is_focused())
return;
node->document().invalidate_display_list();
node->document().update_layout();
if (node->paintable()) {
@ -1112,6 +1114,8 @@ void Document::update_layout()
if (m_created_for_appropriate_template_contents)
return;
invalidate_display_list();
auto* document_element = this->document_element();
auto viewport_rect = navigable->viewport_rect();
@ -1250,6 +1254,9 @@ void Document::update_style()
style_computer().reset_ancestor_filter();
auto invalidation = update_style_recursively(*this, style_computer());
if (!invalidation.is_none()) {
invalidate_display_list();
}
if (invalidation.rebuild_layout_tree) {
invalidate_layout();
} else {
@ -1266,6 +1273,8 @@ void Document::update_animated_style_if_needed()
if (!m_needs_animated_style_update)
return;
invalidate_display_list();
for (auto& timeline : m_associated_animation_timelines) {
for (auto& animation : timeline->associated_animations()) {
if (auto effect = animation->effect(); effect && effect->target())
@ -5393,8 +5402,24 @@ void Document::set_needs_display(CSSPixelRect const&)
}
}
void Document::invalidate_display_list()
{
m_cached_display_list.clear();
auto navigable = this->navigable();
if (!navigable)
return;
if (navigable->container()) {
navigable->container()->document().invalidate_display_list();
}
}
RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config)
{
if (m_cached_display_list && m_cached_display_list_paint_config == config)
return m_cached_display_list;
auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list);
@ -5436,6 +5461,9 @@ RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config)
m_needs_repaint = false;
m_cached_display_list = display_list;
m_cached_display_list_paint_config = config;
return display_list;
}

View file

@ -708,9 +708,13 @@ public:
bool should_show_line_box_borders { false };
bool has_focus { false };
Optional<Gfx::IntRect> canvas_fill_rect {};
bool operator==(PaintConfig const& other) const = default;
};
RefPtr<Painting::DisplayList> record_display_list(PaintConfig);
void invalidate_display_list();
protected:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
@ -986,6 +990,9 @@ private:
WeakPtr<HTML::Navigable> m_cached_navigable;
bool m_needs_repaint { false };
Optional<PaintConfig> m_cached_display_list_paint_config;
RefPtr<Painting::DisplayList> m_cached_display_list;
};
template<>

View file

@ -98,6 +98,7 @@ void Range::update_associated_selection()
{
if (auto* viewport = m_start_container->document().paintable()) {
viewport->recompute_selection_states();
m_start_container->document().invalidate_display_list();
viewport->set_needs_display();
}

View file

@ -186,6 +186,7 @@ Gfx::Painter* CanvasRenderingContext2D::painter()
if (!canvas_element().bitmap()) {
if (!canvas_element().create_bitmap())
return nullptr;
canvas_element().document().invalidate_display_list();
m_painter = make<Gfx::Painter>(*canvas_element().bitmap());
}
return m_painter.ptr();

View file

@ -22,6 +22,7 @@
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/TokenizedFeatures.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/XHR/FormDataEntry.h>

View file

@ -16,6 +16,7 @@
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
namespace Web::HTML {
@ -1193,6 +1194,12 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::
if (!document)
return;
for (auto& navigable : all_navigables()) {
if (auto active_document = navigable->active_document(); active_document && active_document->paintable()) {
active_document->paintable()->refresh_scroll_state();
}
}
DOM::Document::PaintConfig paint_config;
paint_config.paint_overlay = paint_options.paint_overlay == PaintOptions::PaintOverlay::Yes;
paint_config.should_show_line_box_borders = paint_options.should_show_line_box_borders;