Explorar el Código

LibWeb: Add fast path to calculate invalidations for animated css props

- Compare only the animated properties
- Clone only the hash map containing animated properties, instead of
  the entire StyleProperties.

Reduces `KeyframeEffect::update_style_properties()` from 10% to 3% in
GitHub profiles.
Aliaksandr Kalenik hace 1 año
padre
commit
cf7c933312

+ 23 - 2
Userland/Libraries/LibWeb/Animations/KeyframeEffect.cpp

@@ -871,6 +871,27 @@ void KeyframeEffect::visit_edges(Cell::Visitor& visitor)
         visitor.visit(keyframe);
 }
 
+static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(HashMap<CSS::PropertyID, NonnullRefPtr<CSS::StyleValue const>> const& old_properties, HashMap<CSS::PropertyID, NonnullRefPtr<CSS::StyleValue const>> const& new_properties)
+{
+    CSS::RequiredInvalidationAfterStyleChange invalidation;
+    auto old_and_new_properties = MUST(Bitmap::create(to_underlying(CSS::last_property_id) + 1, 0));
+    for (auto const& [property_id, _] : old_properties)
+        old_and_new_properties.set(to_underlying(property_id), 1);
+    for (auto const& [property_id, _] : new_properties)
+        old_and_new_properties.set(to_underlying(property_id), 1);
+    for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
+        if (!old_and_new_properties.get(i))
+            continue;
+        auto property_id = static_cast<CSS::PropertyID>(i);
+        auto old_value = old_properties.get(property_id).value_or({});
+        auto new_value = new_properties.get(property_id).value_or({});
+        if (!old_value && !new_value)
+            continue;
+        invalidation |= compute_property_invalidation(property_id, old_value, new_value);
+    }
+    return invalidation;
+}
+
 void KeyframeEffect::update_style_properties()
 {
     if (!target())
@@ -886,7 +907,7 @@ void KeyframeEffect::update_style_properties()
     if (!style)
         return;
 
-    auto style_before_animation_update = style->clone();
+    auto animated_properties_before_update = style->animated_property_values();
 
     auto& document = target()->document();
     document.style_computer().collect_animation_into(*this, *style, CSS::StyleComputer::AnimationRefresh::Yes);
@@ -908,7 +929,7 @@ void KeyframeEffect::update_style_properties()
         return IterationDecision::Continue;
     });
 
-    auto invalidation = DOM::Element::compute_required_invalidation(style_before_animation_update, *style);
+    auto invalidation = compute_required_invalidation(animated_properties_before_update, style->animated_property_values());
 
     if (target()->layout_node())
         target()->layout_node()->apply_style(*style);

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -89,6 +89,7 @@ set(SOURCES
     CSS/Serialize.cpp
     CSS/Size.cpp
     CSS/StyleComputer.cpp
+    CSS/StyleInvalidation.cpp
     CSS/StyleProperties.cpp
     CSS/StyleProperty.cpp
     CSS/StyleSheet.cpp

+ 61 - 0
Userland/Libraries/LibWeb/CSS/StyleInvalidation.cpp

@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/CSS/StyleInvalidation.h>
+#include <LibWeb/CSS/StyleProperties.h>
+
+namespace Web::CSS {
+
+RequiredInvalidationAfterStyleChange compute_property_invalidation(CSS::PropertyID property_id, RefPtr<CSS::StyleValue const> const& old_value, RefPtr<CSS::StyleValue const> const& new_value)
+{
+    RequiredInvalidationAfterStyleChange invalidation;
+
+    bool const property_value_changed = (!old_value || !new_value) || *old_value != *new_value;
+    if (!property_value_changed)
+        return invalidation;
+
+    // NOTE: If the computed CSS display property changes, we have to rebuild the entire layout tree.
+    //       In the future, we should figure out ways to rebuild a smaller part of the tree.
+    if (property_id == CSS::PropertyID::Display) {
+        return RequiredInvalidationAfterStyleChange::full();
+    }
+
+    // NOTE: If one of the overflow properties change, we rebuild the entire layout tree.
+    //       This ensures that overflow propagation from root/body to viewport happens correctly.
+    //       In the future, we can make this invalidation narrower.
+    if (property_id == CSS::PropertyID::OverflowX || property_id == CSS::PropertyID::OverflowY) {
+        return RequiredInvalidationAfterStyleChange::full();
+    }
+
+    // OPTIMIZATION: Special handling for CSS `visibility`:
+    if (property_id == CSS::PropertyID::Visibility) {
+        // We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
+        if ((old_value && old_value->to_identifier() == CSS::ValueID::Collapse) != (new_value && new_value->to_identifier() == CSS::ValueID::Collapse))
+            invalidation.relayout = true;
+        // Of course, we still have to repaint on any visibility change.
+        invalidation.repaint = true;
+    } else if (CSS::property_affects_layout(property_id)) {
+        invalidation.relayout = true;
+    }
+
+    if (property_id == CSS::PropertyID::Opacity && old_value && new_value) {
+        // OPTIMIZATION: An element creates a stacking context when its opacity changes from 1 to less than 1
+        //               and stops to create one when opacity returns to 1. So stacking context tree rebuild is
+        //               not required for opacity changes within the range below 1.
+        auto old_value_opacity = CSS::StyleProperties::resolve_opacity_value(*old_value);
+        auto new_value_opacity = CSS::StyleProperties::resolve_opacity_value(*new_value);
+        if (old_value_opacity != new_value_opacity && (old_value_opacity == 1 || new_value_opacity == 1)) {
+            invalidation.rebuild_stacking_context_tree = true;
+        }
+    } else if (CSS::property_affects_stacking_context(property_id)) {
+        invalidation.rebuild_stacking_context_tree = true;
+    }
+    invalidation.repaint = true;
+
+    return invalidation;
+}
+
+}

+ 33 - 0
Userland/Libraries/LibWeb/CSS/StyleInvalidation.h

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/PropertyID.h>
+
+namespace Web::CSS {
+
+struct RequiredInvalidationAfterStyleChange {
+    bool repaint : 1 { false };
+    bool rebuild_stacking_context_tree : 1 { false };
+    bool relayout : 1 { false };
+    bool rebuild_layout_tree : 1 { false };
+
+    void operator|=(RequiredInvalidationAfterStyleChange const& other)
+    {
+        repaint |= other.repaint;
+        rebuild_stacking_context_tree |= other.rebuild_stacking_context_tree;
+        relayout |= other.relayout;
+        rebuild_layout_tree |= other.rebuild_layout_tree;
+    }
+
+    [[nodiscard]] bool is_none() const { return !repaint && !rebuild_stacking_context_tree && !relayout && !rebuild_layout_tree; }
+    static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; }
+};
+
+RequiredInvalidationAfterStyleChange compute_property_invalidation(CSS::PropertyID property_id, RefPtr<CSS::StyleValue const> const& old_value, RefPtr<CSS::StyleValue const> const& new_value);
+
+}

+ 1 - 12
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -34,17 +34,6 @@
 
 namespace Web::CSS {
 
-NonnullRefPtr<StyleProperties> StyleProperties::clone() const
-{
-    auto clone = adopt_ref(*new StyleProperties);
-    clone->m_property_values = m_property_values;
-    clone->m_animated_property_values = m_animated_property_values;
-    clone->m_font_list = m_font_list;
-    clone->m_line_height = m_line_height;
-    clone->m_math_depth = m_math_depth;
-    return clone;
-}
-
 bool StyleProperties::is_property_important(CSS::PropertyID property_id) const
 {
     return m_property_values[to_underlying(property_id)].style && m_property_values[to_underlying(property_id)].important == Important::Yes;
@@ -289,7 +278,7 @@ Optional<int> StyleProperties::z_index() const
     return {};
 }
 
-static float resolve_opacity_value(CSS::StyleValue const& value)
+float StyleProperties::resolve_opacity_value(CSS::StyleValue const& value)
 {
     float unclamped_opacity = 1.0f;
 

+ 3 - 2
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -23,8 +23,6 @@ public:
 
     static NonnullRefPtr<StyleProperties> create() { return adopt_ref(*new StyleProperties); }
 
-    NonnullRefPtr<StyleProperties> clone() const;
-
     template<typename Callback>
     inline void for_each_property(Callback callback) const
     {
@@ -55,6 +53,7 @@ public:
     auto& properties() { return m_property_values; }
     auto const& properties() const { return m_property_values; }
 
+    HashMap<CSS::PropertyID, NonnullRefPtr<StyleValue const>> const& animated_property_values() const { return m_animated_property_values; }
     void reset_animated_properties();
 
     bool is_property_important(CSS::PropertyID property_id) const;
@@ -182,6 +181,8 @@ public:
 
     static NonnullRefPtr<Gfx::Font const> font_fallback(bool monospace, bool bold);
 
+    static float resolve_opacity_value(CSS::StyleValue const& value);
+
 private:
     friend class StyleComputer;
 

+ 2 - 2
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -1116,10 +1116,10 @@ void Document::update_layout()
     m_layout_update_timer->stop();
 }
 
-[[nodiscard]] static Element::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node)
+[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node)
 {
     bool const needs_full_style_update = node.document().needs_full_style_update();
-    Element::RequiredInvalidationAfterStyleChange invalidation;
+    CSS::RequiredInvalidationAfterStyleChange invalidation;
 
     // NOTE: If the current node has `display:none`, we can disregard all invalidation
     //       caused by its children, as they will not be rendered anyway.

+ 6 - 45
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -492,9 +492,9 @@ void Element::attribute_changed(FlyString const& name, Optional<String> const& v
     }
 }
 
-Element::RequiredInvalidationAfterStyleChange Element::compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
+static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
 {
-    Element::RequiredInvalidationAfterStyleChange invalidation;
+    CSS::RequiredInvalidationAfterStyleChange invalidation;
 
     if (!old_style.computed_font_list().equals(new_style.computed_font_list()))
         invalidation.relayout = true;
@@ -506,51 +506,12 @@ Element::RequiredInvalidationAfterStyleChange Element::compute_required_invalida
         if (!old_value && !new_value)
             continue;
 
-        bool const property_value_changed = (!old_value || !new_value) || *old_value != *new_value;
-        if (!property_value_changed)
-            continue;
-
-        // NOTE: If the computed CSS display property changes, we have to rebuild the entire layout tree.
-        //       In the future, we should figure out ways to rebuild a smaller part of the tree.
-        if (property_id == CSS::PropertyID::Display) {
-            return Element::RequiredInvalidationAfterStyleChange::full();
-        }
-
-        // NOTE: If one of the overflow properties change, we rebuild the entire layout tree.
-        //       This ensures that overflow propagation from root/body to viewport happens correctly.
-        //       In the future, we can make this invalidation narrower.
-        if (property_id == CSS::PropertyID::OverflowX || property_id == CSS::PropertyID::OverflowY) {
-            return Element::RequiredInvalidationAfterStyleChange::full();
-        }
-
-        // OPTIMIZATION: Special handling for CSS `visibility`:
-        if (property_id == CSS::PropertyID::Visibility) {
-            // We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
-            if ((old_value && old_value->to_identifier() == CSS::ValueID::Collapse) != (new_value && new_value->to_identifier() == CSS::ValueID::Collapse))
-                invalidation.relayout = true;
-            // Of course, we still have to repaint on any visibility change.
-            invalidation.repaint = true;
-        } else if (CSS::property_affects_layout(property_id)) {
-            invalidation.relayout = true;
-        }
-        if (property_id == CSS::PropertyID::Opacity) {
-            // OPTIMIZATION: An element creates a stacking context when its opacity changes from 1 to less than 1
-            //               and stops to create one when opacity returns to 1. So stacking context tree rebuild is
-            //               not required for opacity changes within the range below 1.
-            auto old_value_opacity = old_style.opacity();
-            auto new_value_opacity = new_style.opacity();
-            if (old_value_opacity != new_value_opacity && (old_value_opacity == 1 || new_value_opacity == 1)) {
-                invalidation.rebuild_stacking_context_tree = true;
-            }
-        } else if (CSS::property_affects_stacking_context(property_id)) {
-            invalidation.rebuild_stacking_context_tree = true;
-        }
-        invalidation.repaint = true;
+        invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value);
     }
     return invalidation;
 }
 
-Element::RequiredInvalidationAfterStyleChange Element::recompute_style()
+CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
 {
     set_needs_style_update(false);
     VERIFY(parent());
@@ -565,11 +526,11 @@ Element::RequiredInvalidationAfterStyleChange Element::recompute_style()
             new_computed_css_values->set_property(CSS::PropertyID::TextAlign, CSS::IdentifierStyleValue::create(CSS::ValueID::Start));
     }
 
-    RequiredInvalidationAfterStyleChange invalidation;
+    CSS::RequiredInvalidationAfterStyleChange invalidation;
     if (m_computed_css_values)
         invalidation = compute_required_invalidation(*m_computed_css_values, *new_computed_css_values);
     else
-        invalidation = RequiredInvalidationAfterStyleChange::full();
+        invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
 
     if (invalidation.is_none())
         return invalidation;

+ 2 - 21
Userland/Libraries/LibWeb/DOM/Element.h

@@ -12,6 +12,7 @@
 #include <LibWeb/Bindings/Intrinsics.h>
 #include <LibWeb/Bindings/ShadowRootPrototype.h>
 #include <LibWeb/CSS/Selector.h>
+#include <LibWeb/CSS/StyleInvalidation.h>
 #include <LibWeb/CSS/StyleProperty.h>
 #include <LibWeb/DOM/ChildNode.h>
 #include <LibWeb/DOM/NonDocumentTypeChildNode.h>
@@ -148,27 +149,7 @@ public:
     void run_attribute_change_steps(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_);
     virtual void attribute_changed(FlyString const& name, Optional<String> const& value);
 
-    struct [[nodiscard]] RequiredInvalidationAfterStyleChange {
-        bool repaint { false };
-        bool rebuild_stacking_context_tree { false };
-        bool relayout { false };
-        bool rebuild_layout_tree { false };
-
-        void operator|=(RequiredInvalidationAfterStyleChange const& other)
-        {
-            repaint |= other.repaint;
-            rebuild_stacking_context_tree |= other.rebuild_stacking_context_tree;
-            relayout |= other.relayout;
-            rebuild_layout_tree |= other.rebuild_layout_tree;
-        }
-
-        [[nodiscard]] bool is_none() const { return !repaint && !rebuild_stacking_context_tree && !relayout && !rebuild_layout_tree; }
-        static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; }
-    };
-
-    static Element::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style);
-
-    RequiredInvalidationAfterStyleChange recompute_style();
+    CSS::RequiredInvalidationAfterStyleChange recompute_style();
 
     Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element() const { return m_use_pseudo_element; }
     void set_use_pseudo_element(Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element) { m_use_pseudo_element = move(use_pseudo_element); }