瀏覽代碼

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/
Aliaksandr Kalenik 11 月之前
父節點
當前提交
18fc23b3d6

+ 29 - 1
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -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;
 }
 

+ 7 - 0
Userland/Libraries/LibWeb/DOM/Document.h

@@ -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<>

+ 1 - 0
Userland/Libraries/LibWeb/DOM/Range.cpp

@@ -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();
     }
 

+ 1 - 0
Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp

@@ -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();

+ 1 - 0
Userland/Libraries/LibWeb/HTML/Navigable.h

@@ -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>
 

+ 7 - 0
Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp

@@ -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;