Bug 768440 Part 2: Animate CSS Transitions on the compositor r=roc,dbaron

This commit is contained in:
David Zbarsky
2012-07-31 10:28:22 -07:00
parent c1125ae35c
commit 6a56e516b5
8 changed files with 261 additions and 124 deletions

View File

@@ -11,6 +11,7 @@
#include "nsChangeHint.h"
#include "nsINode.h"
#include "nsIDocument.h" // for IsInHTMLDocument
#include "nsCSSProperty.h"
// Forward declarations
class nsIAtom;

View File

@@ -39,6 +39,7 @@
#include "nsSVGClipPathFrame.h"
#include "sampler.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
#include "nsIViewManager.h"
#include "mozilla/StandardInteger.h"
@@ -267,13 +268,85 @@ ToTimingFunction(css::ComputedTimingFunction& aCTF)
}
static void
AddTransformAnimations(ElementAnimations* ea, Layer* aLayer,
const nsPoint& aOrigin)
AddAnimationsForProperty(nsIFrame* aFrame, nsCSSProperty aProperty,
ElementAnimation* ea, Layer* aLayer,
AnimationData& aData)
{
if (!ea)
return;
NS_ASSERTION(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
nsIFrame* frame = ea->mElement->GetPrimaryFrame();
nsStyleContext* styleContext = aFrame->GetStyleContext();
nsPresContext* presContext = aFrame->PresContext();
nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(aFrame);
float scale = nsDeviceContext::AppUnitsPerCSSPixel();
float iterations = ea->mIterationCount != NS_IEEEPositiveInfinity()
? ea->mIterationCount : -1;
for (PRUint32 propIdx = 0; propIdx < ea->mProperties.Length(); propIdx++) {
AnimationProperty* property = &ea->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (aProperty != property->mProperty) {
continue;
}
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
if (aProperty == eCSSProperty_transform) {
nsCSSValueList* list = segment->mFromValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> fromFunctions;
AddTransformFunctions(list, styleContext,
presContext, bounds,
scale, fromFunctions);
list = segment->mToValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> toFunctions;
AddTransformFunctions(list, styleContext,
presContext, bounds,
scale, toFunctions);
segments.AppendElement(AnimationSegment(fromFunctions, toFunctions,
segment->mFromKey, segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
} else if (aProperty == eCSSProperty_opacity) {
segments.AppendElement(AnimationSegment(Opacity(segment->mFromValue.GetFloatValue()),
Opacity(segment->mToValue.GetFloatValue()),
segment->mFromKey,
segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
}
}
aLayer->AddAnimation(Animation(ea->mStartTime,
ea->mIterationDuration,
segments,
iterations,
ea->mDirection,
aData));
}
}
static void
AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayItem* aItem,
nsCSSProperty aProperty)
{
aLayer->ClearAnimations();
nsIFrame* frame = aItem->GetUnderlyingFrame();
nsIContent* aContent = frame->GetContent();
ElementTransitions* et =
nsTransitionManager::GetTransitionsForCompositor(aContent, aProperty);
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(aContent, aProperty);
if (!ea && !et) {
return;
}
mozilla::TimeStamp currentTime =
frame->PresContext()->RefreshDriver()->MostRecentRefresh();
AnimationData data;
if (aProperty == eCSSProperty_transform) {
nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(frame);
float scale = nsDeviceContext::AppUnitsPerCSSPixel();
gfxPoint3D offsetToTransformOrigin =
@@ -288,96 +361,55 @@ AddTransformAnimations(ElementAnimations* ea, Layer* aLayer,
perspective = disp->mChildPerspective.GetCoordValue();
}
}
nsPoint origin = aItem->ToReferenceFrame();
data = TransformData(origin, offsetToTransformOrigin,
offsetToPerspectiveOrigin, bounds, perspective);
} else if (aProperty == eCSSProperty_opacity) {
data = null_t();
}
if (et) {
for (PRUint32 tranIdx = 0; tranIdx < et->mPropertyTransitions.Length(); tranIdx++) {
ElementPropertyTransition* pt = &et->mPropertyTransitions[tranIdx];
if (!pt->CanPerformOnCompositor(et->mElement, currentTime)) {
continue;
}
ElementAnimation anim;
anim.mIterationCount = 1;
anim.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
anim.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_NONE;
anim.mStartTime = pt->mStartTime;
anim.mIterationDuration = pt->mDuration;
AnimationProperty& prop = *anim.mProperties.AppendElement();
prop.mProperty = pt->mProperty;
AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
segment.mFromKey = 0;
segment.mToKey = 1;
segment.mFromValue = pt->mStartValue;
segment.mToValue = pt->mEndValue;
segment.mTimingFunction = pt->mTimingFunction;
AddAnimationsForProperty(frame, aProperty, &anim,
aLayer, data);
}
}
if (ea) {
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
if (!anim->CanPerformOnCompositor(ea->mElement, TimeStamp::Now())) {
if (!anim->CanPerformOnCompositor(ea->mElement, currentTime)) {
continue;
}
float iterations = anim->mIterationCount != NS_IEEEPositiveInfinity()
? anim->mIterationCount : -1;
for (PRUint32 propIdx = 0; propIdx < anim->mProperties.Length(); propIdx++) {
AnimationProperty* property = &anim->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (property->mProperty != eCSSProperty_transform) {
continue;
}
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
nsCSSValueList* list = segment->mFromValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> fromFunctions;
AddTransformFunctions(list, frame->GetStyleContext(),
frame->PresContext(), bounds,
scale, fromFunctions);
list = segment->mToValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> toFunctions;
AddTransformFunctions(list, frame->GetStyleContext(),
frame->PresContext(), bounds,
scale, toFunctions);
segments.AppendElement(AnimationSegment(fromFunctions, toFunctions,
segment->mFromKey, segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
}
if (segments.Length() == 0) {
continue;
}
aLayer->AddAnimation(Animation(anim->mStartTime,
anim->mIterationDuration,
segments,
iterations,
anim->mDirection,
TransformData(aOrigin, offsetToTransformOrigin,
offsetToPerspectiveOrigin,
bounds, perspective)));
AddAnimationsForProperty(frame, aProperty, anim,
aLayer, data);
}
}
}
static void
AddOpacityAnimations(ElementAnimations* ea, Layer* aLayer)
{
if (!ea)
return;
NS_ASSERTION(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
float iterations = anim->mIterationCount != NS_IEEEPositiveInfinity()
? anim->mIterationCount : -1;
if (!anim->CanPerformOnCompositor(ea->mElement, TimeStamp::Now())) {
continue;
}
for (PRUint32 propIdx = 0; propIdx < anim->mProperties.Length(); propIdx++) {
AnimationProperty* property = &anim->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (property->mProperty != eCSSProperty_opacity) {
continue;
}
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
segments.AppendElement(AnimationSegment(Opacity(segment->mFromValue.GetFloatValue()),
Opacity(segment->mToValue.GetFloatValue()),
segment->mFromKey,
segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
}
aLayer->AddAnimation(Animation(anim->mStartTime,
anim->mIterationDuration,
segments,
iterations,
anim->mDirection,
null_t()));
}
}
}
nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
Mode aMode, bool aBuildCaret)
: mReferenceFrame(aReferenceFrame),
@@ -2321,12 +2353,7 @@ nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
return nullptr;
container->SetOpacity(mFrame->GetStyleDisplay()->mOpacity);
container->ClearAnimations();
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_opacity);
AddOpacityAnimations(ea, container);
AddAnimationsAndTransitionsToLayer(container, this, eCSSProperty_opacity);
return container.forget();
}
@@ -2354,7 +2381,7 @@ nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
!IsItemTooSmallForActiveLayer(this))
return LAYER_ACTIVE;
if (mFrame->GetContent()) {
if (nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_opacity)) {
return LAYER_ACTIVE;
}
@@ -3338,12 +3365,7 @@ already_AddRefed<Layer> nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBu
container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_PRESERVE_3D);
}
container->ClearAnimations();
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_transform);
AddTransformAnimations(ea, container, ToReferenceFrame());
AddAnimationsAndTransitionsToLayer(container, this, eCSSProperty_transform);
return container.forget();
}
@@ -3359,7 +3381,7 @@ nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
if (!GetTransform(mFrame->PresContext()->AppUnitsPerDevPixel()).Is2D() || mFrame->Preserves3D())
return LAYER_ACTIVE;
if (mFrame->GetContent()) {
if (nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_transform)) {
return LAYER_ACTIVE;
}

View File

@@ -83,6 +83,8 @@
#endif
#include "sampler.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
using namespace mozilla;
using namespace mozilla::layers;
@@ -113,6 +115,32 @@ static ContentMap& GetContentMap() {
return *sContentMap;
}
bool
nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty)
{
if (!aContent->MayHaveAnimations())
return false;
ElementAnimations* animations =
static_cast<ElementAnimations*>(aContent->GetProperty(nsGkAtoms::animationsProperty));
if (animations) {
bool propertyMatches = animations->HasAnimationOfProperty(aProperty);
if (propertyMatches && animations->CanPerformOnCompositorThread()) {
return true;
}
}
ElementTransitions* transitions =
static_cast<ElementTransitions*>(aContent->GetProperty(nsGkAtoms::transitionsProperty));
if (transitions) {
bool propertyMatches = transitions->HasTransitionOfProperty(aProperty);
if (propertyMatches && transitions->CanPerformOnCompositorThread()) {
return true;
}
}
return false;
}
bool
nsLayoutUtils::Are3DTransformsEnabled()

View File

@@ -1497,6 +1497,13 @@ public:
nsMallocSizeOfFun aMallocSizeOf,
bool clear);
/**
* Returns true if the content node has animations or transitions that can be
* performed on the compositor.
*/
static bool HasAnimationsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty);
/**
* Checks if CSS 3D transforms are currently enabled.
*/

View File

@@ -93,6 +93,7 @@
#include "nsAbsoluteContainingBlock.h"
#include "nsFontInflationData.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
@@ -940,14 +941,16 @@ nsIFrame::IsTransformed() const
(GetStyleDisplay()->HasTransform() ||
IsSVGTransformed() ||
(mContent &&
nsAnimationManager::GetAnimationsForCompositor(mContent, eCSSProperty_transform))));
nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_transform))));
}
bool
nsIFrame::HasOpacity() const
{
return GetStyleDisplay()->mOpacity < 1.0f || (mContent &&
nsAnimationManager::GetAnimationsForCompositor(mContent, eCSSProperty_opacity));
nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_opacity));
}
bool
@@ -1774,9 +1777,12 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
nsRect clipPropClip;
const nsStyleDisplay* disp = GetStyleDisplay();
// We can stop right away if this is a zero-opacity stacking context and
// we're painting.
if (disp->mOpacity == 0.0 && aBuilder->IsForPainting())
// we're painting, and we're not animating opacity.
if (disp->mOpacity == 0.0 && aBuilder->IsForPainting() &&
!nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_opacity)) {
return NS_OK;
}
bool applyClipPropClipping =
ApplyClipPropClipping(aBuilder, disp, this, &clipPropClip);
@@ -1921,7 +1927,6 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
// resultList was emptied
resultList.AppendToTop(item);
}
/* If there are any SVG effects, wrap the list up in an SVG effects item
* (which also handles CSS group opacity). Note that we create an SVG effects
* item even if resultList is empty, since a filter can produce graphical

View File

@@ -22,6 +22,8 @@
#include "nsEventDispatcher.h"
#include "nsGUIEvent.h"
#include "mozilla/dom/Element.h"
#include "nsIFrame.h"
#include "nsCSSFrameConstructor.h"
using mozilla::TimeStamp;
using mozilla::TimeDuration;
@@ -36,7 +38,6 @@ ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, nsIAtom
{
}
double
ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
{
@@ -107,6 +108,41 @@ ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
}
}
bool
ElementPropertyTransition::CanPerformOnCompositor(mozilla::dom::Element* aElement,
TimeStamp aTime) const {
return css::CommonElementAnimationData::
CanAnimatePropertyOnCompositor(aElement, mProperty) && !IsRemovedSentinel() &&
mStartTime < aTime && aTime < mStartTime + mDuration;
}
bool
ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const
{
for (PRUint32 tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
if (aProperty == mPropertyTransitions[tranIdx].mProperty) {
return true;
}
}
return false;
}
bool
ElementTransitions::CanPerformOnCompositorThread() const
{
for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition &pt = mPropertyTransitions[i];
if (pt.IsRemovedSentinel()) {
continue;
}
if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(mElement,
pt.mProperty)) {
return false;
}
}
return true;
}
/*****************************************************************************
* nsTransitionManager *
*****************************************************************************/
@@ -298,10 +334,6 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
// rule.
nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
if (!coverRule) {
NS_WARNING("out of memory");
return nullptr;
}
nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
@@ -347,9 +379,19 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
pt.mStartValue) &&
ExtractComputedValueForTransition(aProperty, aNewStyleContext,
pt.mEndValue);
bool haveChange = pt.mStartValue != pt.mEndValue;
bool haveOMTA = false;
if (!aNewStyleContext->GetPseudoType()) {
ElementTransitions* et = nsTransitionManager::GetTransitions(aElement);
if (et) {
haveOMTA = et->CanPerformOnCompositorThread();
}
}
bool shouldAnimate =
haveValues &&
pt.mStartValue != pt.mEndValue &&
(haveChange || haveOMTA) &&
// Check that we can interpolate between these values
// (If this is ever a performance problem, we could add a
// CanInterpolate method, but it seems fine for now.)
@@ -450,6 +492,7 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
// reduce positive delays.
if (delay < 0.0f)
delay *= valuePortion;
duration *= valuePortion;
pt.mStartForReversingTest = oldPT.mEndValue;
@@ -461,7 +504,6 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
pt.mStartTime = mostRecentRefresh + TimeDuration::FromMilliseconds(delay);
pt.mDuration = TimeDuration::FromMilliseconds(duration);
pt.mTimingFunction.Init(tf);
if (!aElementTransitions) {
aElementTransitions =
GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
@@ -495,6 +537,7 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
nsCSSPseudoElements::ePseudo_NotPseudoElement ?
eRestyle_Self : eRestyle_Subtree;
presContext->PresShell()->RestyleForAnimation(aElement, hint);
// XXXdz: invalidate the frame here, once animations are throttled.
*aStartedAny = true;
aWhichStarted->AddProperty(aProperty);
@@ -535,6 +578,9 @@ nsTransitionManager::GetElementTransitions(dom::Element *aElement,
delete et;
return nullptr;
}
if (propName == nsGkAtoms::transitionsProperty) {
aElement->SetMayHaveAnimations();
}
AddElementData(et);
}
@@ -688,8 +734,6 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
// completion. See comment below.
et->mPropertyTransitions.RemoveElementAt(i);
} else if (pt.mStartTime + pt.mDuration <= aTime) {
// This transition has completed.
// Fire transitionend events only for transitions on elements
// and not those on pseudo-elements, since we can't target an
// event at pseudo-elements.
@@ -723,6 +767,9 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
eRestyle_Self : eRestyle_Subtree;
mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
// XXXdz: if we have started a transition since the last tick and are
// performing the transition off the main thread, we need to invalidate
// the frame once we start throttling animation ticks.
if (et->mPropertyTransitions.IsEmpty()) {
et->Destroy();

View File

@@ -22,6 +22,8 @@ struct nsTransition;
struct ElementPropertyTransition
{
ElementPropertyTransition() {}
nsCSSProperty mProperty;
nsStyleAnimation::Value mStartValue, mEndValue;
mozilla::TimeStamp mStartTime; // actual start plus transition delay
@@ -62,6 +64,9 @@ struct ElementPropertyTransition
// assign the null time stamp
mStartTime = mozilla::TimeStamp();
}
bool CanPerformOnCompositor(mozilla::dom::Element* aElement,
mozilla::TimeStamp aTime) const;
};
struct ElementTransitions : public mozilla::css::CommonElementAnimationData
@@ -71,8 +76,10 @@ struct ElementTransitions : public mozilla::css::CommonElementAnimationData
void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime);
bool HasTransitionOfProperty(nsCSSProperty aProperty) const;
// True if this animation can be performed on the compositor thread.
// virtual CanPerformOnCompositorThread() const;
bool CanPerformOnCompositorThread() const;
// Either zero or one for each CSS property:
nsTArray<ElementPropertyTransition> mPropertyTransitions;
@@ -97,6 +104,26 @@ public:
{
}
static ElementTransitions* GetTransitions(nsIContent* aContent) {
return static_cast<ElementTransitions*>
(aContent->GetProperty(nsGkAtoms::transitionsProperty));
}
static ElementTransitions*
GetTransitionsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty)
{
if (!aContent->MayHaveAnimations())
return nullptr;
ElementTransitions* transitions = GetTransitions(aContent);
if (!transitions ||
!transitions->HasTransitionOfProperty(aProperty) ||
!transitions->CanPerformOnCompositorThread()) {
return nullptr;
}
return transitions;
}
/**
* StyleContextChanged
*