1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-10 18:10:56 +09:00

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.
This commit is contained in:
Aliaksandr Kalenik 2024-03-19 12:02:06 +01:00 committed by Andreas Kling
parent ce2bfb4a12
commit cf7c933312
Notes: sideshowbarker 2024-07-16 22:16:50 +09:00
9 changed files with 132 additions and 84 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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