Bug 1844466 - Speed up and simplify cumulative changehint computation. r=boris,hiro

As a bonus we now can throttle some additive and visibility animations
more properly, because before we couldn't compute a change hint for
those but now we don't need to.

Differential Revision: https://phabricator.services.mozilla.com/D185175
This commit is contained in:
Emilio Cobos Álvarez 2023-08-03 09:04:11 +00:00
Родитель 5502bf7db9
Коммит 070c1a117c
9 изменённых файлов: 86 добавлений и 262 удалений

Просмотреть файл

@ -446,11 +446,6 @@ void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle,
if (baseStylesChanged) {
RequestRestyle(EffectCompositor::RestyleType::Layer);
}
// Check if we need to update the cumulative change hint because we now have
// style data.
if (mNeedsStyleData && mTarget && mTarget.mElement->HasServoData()) {
CalculateCumulativeChangeHint(aStyle);
}
return;
}
@ -466,23 +461,13 @@ void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle,
mProperties = std::move(properties);
UpdateEffectSet();
mHasCurrentColor = false;
mCumulativeChanges = {};
for (AnimationProperty& property : mProperties) {
property.mIsRunningOnCompositor =
runningOnCompositorProperties.HasProperty(property.mProperty);
if (property.mProperty == eCSSProperty_background_color &&
!mHasCurrentColor) {
if (HasCurrentColor(property.mSegments)) {
mHasCurrentColor = true;
break;
}
}
CalculateCumulativeChangesForProperty(property);
}
CalculateCumulativeChangeHint(aStyle);
MarkCascadeNeedsUpdate();
if (mAnimation) {
@ -1098,10 +1083,9 @@ already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
// aSource's timing object can be assumed valid.
RefPtr<KeyframeEffect> effect =
new KeyframeEffect(doc, OwningAnimationTarget{aSource.mTarget}, aSource);
// Copy cumulative change hint. mCumulativeChangeHint should be the same as
// the source one because both of targets are the same.
effect->mCumulativeChangeHint = aSource.mCumulativeChangeHint;
// Copy cumulative changes. mCumulativeChangeHint should be the same as the
// source one because both of targets are the same.
effect->mCumulativeChanges = aSource.mCumulativeChanges;
return effect.forget();
}
@ -1774,104 +1758,50 @@ void KeyframeEffect::SetPerformanceWarning(
}
}
already_AddRefed<const ComputedStyle>
KeyframeEffect::CreateComputedStyleForAnimationValue(
nsCSSPropertyID aProperty, const AnimationValue& aValue,
nsPresContext* aPresContext, const ComputedStyle* aBaseComputedStyle) {
MOZ_ASSERT(aBaseComputedStyle,
"CreateComputedStyleForAnimationValue needs to be called "
"with a valid ComputedStyle");
Element* elementForResolve = AnimationUtils::GetElementForRestyle(
mTarget.mElement, mTarget.mPseudoType);
// The element may be null if, for example, we target a pseudo-element that no
// longer exists.
if (!elementForResolve) {
return nullptr;
void KeyframeEffect::CalculateCumulativeChangesForProperty(
const AnimationProperty& aProperty) {
constexpr auto kInterestingFlags =
CSSPropFlags::AffectsLayout | CSSPropFlags::AffectsOverflow;
if (aProperty.mProperty == eCSSProperty_opacity) {
mCumulativeChanges.mOpacity = true;
return; // We know opacity is visual-only.
}
ServoStyleSet* styleSet = aPresContext->StyleSet();
return styleSet->ResolveServoStyleByAddingAnimation(
elementForResolve, aBaseComputedStyle, aValue.mServo);
}
if (aProperty.mProperty == eCSSProperty_visibility) {
mCumulativeChanges.mVisibility = true;
return; // We know visibility is visual-only.
}
void KeyframeEffect::CalculateCumulativeChangeHint(
const ComputedStyle* aComputedStyle) {
mCumulativeChangeHint = nsChangeHint(0);
mNeedsStyleData = false;
if (aProperty.mProperty == eCSSProperty_background_color) {
if (!mCumulativeChanges.mHasBackgroundColorCurrentColor) {
mCumulativeChanges.mHasBackgroundColorCurrentColor =
HasCurrentColor(aProperty.mSegments);
}
return; // We know background-color is visual-only.
}
nsPresContext* presContext =
mTarget ? nsContentUtils::GetContextForContent(mTarget.mElement)
: nullptr;
if (!presContext) {
// Change hints make no sense if we're not rendered.
//
// Actually, we cannot even post them anywhere.
mNeedsStyleData = true;
auto flags = nsCSSProps::PropFlags(aProperty.mProperty);
if (!(flags & kInterestingFlags)) {
return; // Property is visual-only.
}
bool anyChange = false;
for (const AnimationPropertySegment& segment : aProperty.mSegments) {
if (!segment.HasReplaceableValues() ||
segment.mFromValue != segment.mToValue) {
// We can't know non-replaceable values until we compose the animation, so
// assume a change there.
anyChange = true;
break;
}
}
if (!anyChange) {
return;
}
for (const AnimationProperty& property : mProperties) {
// For opacity property we don't produce any change hints that are not
// included in nsChangeHint_Hints_CanIgnoreIfNotVisible so we can throttle
// opacity animations regardless of the change they produce. This
// optimization is particularly important since it allows us to throttle
// opacity animations with missing 0%/100% keyframes.
if (property.mProperty == eCSSProperty_opacity) {
continue;
}
for (const AnimationPropertySegment& segment : property.mSegments) {
// In case composite operation is not 'replace' or value is null,
// we can't throttle animations which will not cause any layout changes
// on invisible elements because we can't calculate the change hint for
// such properties until we compose it.
if (!segment.HasReplaceableValues()) {
if (!nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
property.mProperty)) {
mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
return;
}
// We try a little harder to optimize transform animations simply
// because they are so common (the second-most commonly animated
// property at the time of writing). So if we encounter a transform
// segment that needs composing with the underlying value, we just add
// all the change hints a transform animation is known to be able to
// generate.
mCumulativeChangeHint |=
nsChangeHint_ComprehensiveAddOrRemoveTransform |
nsChangeHint_UpdatePostTransformOverflow |
nsChangeHint_UpdateTransformLayer;
continue;
}
RefPtr<const ComputedStyle> fromContext =
CreateComputedStyleForAnimationValue(property.mProperty,
segment.mFromValue, presContext,
aComputedStyle);
if (!fromContext) {
mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
mNeedsStyleData = true;
return;
}
RefPtr<const ComputedStyle> toContext =
CreateComputedStyleForAnimationValue(property.mProperty,
segment.mToValue, presContext,
aComputedStyle);
if (!toContext) {
mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
mNeedsStyleData = true;
return;
}
uint32_t equalStructs = 0;
nsChangeHint changeHint =
fromContext->CalcStyleDifference(*toContext, &equalStructs);
mCumulativeChangeHint |= changeHint;
}
}
mCumulativeChanges.mOverflow |= bool(flags & CSSPropFlags::AffectsOverflow);
mCumulativeChanges.mLayout |= bool(flags & CSSPropFlags::AffectsLayout);
}
void KeyframeEffect::SetAnimation(Animation* aAnimation) {
@ -1903,11 +1833,7 @@ bool KeyframeEffect::CanIgnoreIfNotVisible() const {
if (!StaticPrefs::dom_animations_offscreen_throttling()) {
return false;
}
// FIXME: For further sophisticated optimization we need to check
// change hint on the segment corresponding to computedTiming.progress.
return NS_IsHintSubset(mCumulativeChangeHint,
nsChangeHint_Hints_CanIgnoreIfNotVisible);
return !mCumulativeChanges.mLayout;
}
void KeyframeEffect::MarkCascadeNeedsUpdate() {
@ -2104,7 +2030,7 @@ KeyframeEffect::MatchForCompositor KeyframeEffect::IsMatchForCompositor(
// We can't run this background color animation on the compositor if there
// is any `current-color` keyframe.
if (mHasCurrentColor) {
if (mCumulativeChanges.mHasBackgroundColorCurrentColor) {
aPerformanceWarning = AnimationPerformanceWarning::Type::HasCurrentColor;
return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
}

Просмотреть файл

@ -313,7 +313,7 @@ class KeyframeEffect : public AnimationEffect {
// Cumulative change hint on each segment for each property.
// This is used for deciding the animation is paint-only.
void CalculateCumulativeChangeHint(const ComputedStyle* aStyle);
void CalculateCumulativeChangesForProperty(const AnimationProperty&);
// Returns true if all of animation properties' change hints
// can ignore painting if the animation is not visible.
@ -362,9 +362,7 @@ class KeyframeEffect : public AnimationEffect {
const Nullable<double>& aProgressOnLastCompose,
uint64_t aCurrentIterationOnLastCompose);
bool HasOpacityChange() const {
return mCumulativeChangeHint & nsChangeHint_UpdateOpacityLayer;
}
bool HasOpacityChange() const { return mCumulativeChanges.mOpacity; }
protected:
~KeyframeEffect() override = default;
@ -451,15 +449,6 @@ class KeyframeEffect : public AnimationEffect {
// EffectSet.
bool mInEffectSet = false;
// True if the last time we tried to update the cumulative change hint we
// didn't have style data to do so. We set this flag so that the next time
// our style context changes we know to update the cumulative change hint even
// if our properties haven't changed.
bool mNeedsStyleData = false;
// True if there is any current-color for background color in this keyframes.
bool mHasCurrentColor = false;
// The non-animated values for properties in this effect that contain at
// least one animation value that is composited with the underlying value
// (i.e. it uses the additive or accumulate composite mode).
@ -468,7 +457,27 @@ class KeyframeEffect : public AnimationEffect {
BaseValuesHashmap mBaseValues;
private:
nsChangeHint mCumulativeChangeHint = nsChangeHint{0};
// The cumulative changes of all the animation segments.
struct CumulativeChanges {
// Whether the opacity property is changing.
bool mOpacity : 1;
// Whether the visibility property is changing.
bool mVisibility : 1;
// Whether layout is changing.
bool mLayout : 1;
// Whether overflow is changing.
bool mOverflow : 1;
// True if there is any current-color for background color.
bool mHasBackgroundColorCurrentColor : 1;
CumulativeChanges()
: mOpacity(false),
mVisibility(false),
mLayout(false),
mOverflow(false),
mHasBackgroundColorCurrentColor(false) {}
};
CumulativeChanges mCumulativeChanges;
void ComposeStyleRule(StyleAnimationValueMap& aAnimationValues,
const AnimationProperty& aProperty,
@ -510,17 +519,12 @@ class KeyframeEffect : public AnimationEffect {
// This function is used for updating scroll bars or notifying intersection
// observers reflected by the transform.
bool HasPropertiesThatMightAffectOverflow() const {
return mCumulativeChangeHint &
(nsChangeHint_AddOrRemoveTransform | nsChangeHint_UpdateOverflow |
nsChangeHint_UpdatePostTransformOverflow |
nsChangeHint_UpdateTransformLayer);
return mCumulativeChanges.mOverflow;
}
// Returns true if this effect causes visibility change.
// (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.)
bool HasVisibilityChange() const {
return mCumulativeChangeHint & nsChangeHint_VisibilityChange;
}
bool HasVisibilityChange() const { return mCumulativeChanges.mVisibility; }
};
} // namespace dom

Просмотреть файл

@ -1270,9 +1270,9 @@ waitForAllPaints(() => {
const markers = await observeStyling(5);
is(markers.length, 5,
is(markers.length, 0,
'Additive animation has no keyframe whose offset is 0 or 1 in an ' +
'out-of-view element should not be throttled');
'out-of-view element should be throttled');
await ensureElementRemoval(div);
}
);
@ -1287,9 +1287,9 @@ waitForAllPaints(() => {
const markers = await observeStyling(5);
is(markers.length, 5,
is(markers.length, 0,
'Discrete animation has has no keyframe whose offset is 0 or 1 in an ' +
'out-of-view element should not be throttled');
'out-of-view element should be throttled');
await ensureElementRemoval(div);
});
@ -1305,10 +1305,9 @@ waitForAllPaints(() => {
await waitForAnimationReadyToRestyle(animation);
const markers = await observeStyling(5);
is(markers.length, 5,
is(markers.length, 0,
'visibility animation has no keyframe whose offset is 0 or 1 in an ' +
'out-of-view element and produces change hint other than paint-only ' +
'change hint should not be throttled');
'out-of-view element should be throttled');
await ensureElementRemoval(div);
}
);

Просмотреть файл

@ -425,15 +425,6 @@ static_assert(!(nsChangeHint_Hints_AlwaysHandledForDescendants &
#define NS_STYLE_HINT_REFLOW \
nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints)
#define nsChangeHint_Hints_CanIgnoreIfNotVisible \
nsChangeHint( \
NS_STYLE_HINT_VISUAL | nsChangeHint_NeutralChange | \
nsChangeHint_UpdateOpacityLayer | nsChangeHint_AddOrRemoveTransform | \
nsChangeHint_UpdateContainingBlock | nsChangeHint_UpdateOverflow | \
nsChangeHint_UpdatePostTransformOverflow | \
nsChangeHint_UpdateTransformLayer | nsChangeHint_UpdateUsesOpacity | \
nsChangeHint_UpdateBackgroundPosition | nsChangeHint_VisibilityChange)
// Change hints for added or removed transform style.
//
// If we've added or removed the transform property, we need to reconstruct the

Просмотреть файл

@ -112,6 +112,13 @@ def exposed_on_getcs(prop):
def rules(prop):
return ", ".join('"{}"'.format(rule) for rule in prop.rule_types_allowed_names())
RUST_TO_CPP_FLAGS = {
"CAN_ANIMATE_ON_COMPOSITOR": "CanAnimateOnCompositor",
"AFFECTS_LAYOUT": "AffectsLayout",
"AFFECTS_PAINT": "AffectsPaint",
"AFFECTS_OVERFLOW": "AffectsOverflow",
}
def flags(prop):
result = []
if prop.explicitly_enabled_in_chrome():
@ -122,8 +129,9 @@ def flags(prop):
result.append("Internal")
if prop.enabled_in == "":
result.append("Inaccessible")
if "CAN_ANIMATE_ON_COMPOSITOR" in prop.flags:
result.append("CanAnimateOnCompositor")
for (k, v) in RUST_TO_CPP_FLAGS.items():
if k in prop.flags:
result.append(v)
if exposed_on_getcs(prop):
result.append("ExposedOnGetCS")
if prop.type() == "shorthand" and "SHORTHAND_IN_GETCS" in prop.flags:

Просмотреть файл

@ -1091,15 +1091,6 @@ already_AddRefed<ComputedStyle> ServoStyleSet::GetBaseContextForElement(
.Consume();
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveServoStyleByAddingAnimation(
Element* aElement, const ComputedStyle* aStyle,
StyleAnimationValue* aAnimationValue) {
return Servo_StyleSet_GetComputedValuesByAddingAnimation(
mRawData.get(), aElement, aStyle, &Snapshots(), aAnimationValue)
.Consume();
}
already_AddRefed<StyleAnimationValue> ServoStyleSet::ComputeAnimationValue(
Element* aElement, StyleLockedDeclarationBlock* aDeclarations,
const ComputedStyle* aStyle) {

Просмотреть файл

@ -399,21 +399,6 @@ class ServoStyleSet {
already_AddRefed<ComputedStyle> GetBaseContextForElement(
dom::Element* aElement, const ComputedStyle* aStyle);
// Get a ComputedStyle that represents |aStyle|, but as though it additionally
// matched the rules of the newly added |aAnimaitonaValue|.
//
// We use this function to temporarily generate a ComputedStyle for
// calculating the cumulative change hints.
//
// This must hold:
// The additional rules must be appropriate for the transition
// level of the cascade, which is the highest level of the cascade.
// (This is the case for one current caller, the cover rule used
// for CSS transitions.)
// Note: |aElement| should be the generated element if it is pseudo.
already_AddRefed<ComputedStyle> ResolveServoStyleByAddingAnimation(
dom::Element* aElement, const ComputedStyle* aStyle,
StyleAnimationValue* aAnimationValue);
/**
* Resolve style for a given declaration block with/without the parent style.
* If the parent style is not specified, the document default computed values

Просмотреть файл

@ -10,7 +10,7 @@ use crate::applicable_declarations::{ApplicableDeclarationList, CascadePriority}
use crate::properties::{LonghandIdSet, PropertyDeclarationBlock};
use crate::shared_lock::{Locked, StylesheetGuards};
use crate::stylesheets::layer_rule::LayerOrder;
use servo_arc::{Arc, ArcBorrow};
use servo_arc::ArcBorrow;
use smallvec::SmallVec;
use std::io::{self, Write};
@ -321,29 +321,6 @@ impl RuleTree {
.insert_ordered_rules_from(last.parent().unwrap().clone(), children.drain(..).rev());
rule
}
/// Returns new rule node by adding animation rules at transition level.
/// The additional rules must be appropriate for the transition
/// level of the cascade, which is the highest level of the cascade.
/// (This is the case for one current caller, the cover rule used
/// for CSS transitions.)
pub fn add_animation_rules_at_transition_level(
&self,
path: &StrongRuleNode,
pdb: Arc<Locked<PropertyDeclarationBlock>>,
guards: &StylesheetGuards,
) -> StrongRuleNode {
let mut dummy = false;
self.update_rule_at_level(
CascadeLevel::Transitions,
LayerOrder::root(),
Some(pdb.borrow_arc()),
path,
guards,
&mut dummy,
)
.expect("Should return a valid rule node")
}
}
impl StrongRuleNode {

Просмотреть файл

@ -1159,63 +1159,6 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(
.into()
}
#[no_mangle]
pub extern "C" fn Servo_StyleSet_GetComputedValuesByAddingAnimation(
raw_style_set: &PerDocumentStyleData,
element: &RawGeckoElement,
computed_values: &ComputedValues,
snapshots: *const ServoElementSnapshotTable,
animation_value: &AnimationValue,
) -> Strong<ComputedValues> {
debug_assert!(!snapshots.is_null());
let rules = match computed_values.rules {
None => return Strong::null(),
Some(ref rules) => rules,
};
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let uncomputed_value = animation_value.uncompute();
let doc_data = raw_style_set.borrow();
let with_animations_rules = {
let guards = StylesheetGuards::same(&guard);
let declarations = Arc::new(global_style_data.shared_lock.wrap(
PropertyDeclarationBlock::with_one(uncomputed_value, Importance::Normal),
));
doc_data
.stylist
.rule_tree()
.add_animation_rules_at_transition_level(rules, declarations, &guards)
};
let element = GeckoElement(element);
if element.borrow_data().is_none() {
return Strong::null();
}
let shared = create_shared_context(
&global_style_data,
&guard,
&doc_data.stylist,
TraversalFlags::empty(),
unsafe { &*snapshots },
);
let mut tlc: ThreadLocalStyleContext<GeckoElement> = ThreadLocalStyleContext::new();
let context = StyleContext {
shared: &shared,
thread_local: &mut tlc,
};
resolve_rules_for_element_with_context(
element,
context,
with_animations_rules,
&computed_values,
)
.into()
}
#[no_mangle]
pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue(
computed_values: &ComputedValues,