Files
tubestation/gfx/layers/AnimationHelper.cpp
Brian Birtles 2ada0df47e Bug 1334583 - Pass a separate timeOrigin and startTime for compositor animations; r=hiro
By passing the startTime as a TimeDuration we are able to represent times in the
distant past (and with the same range as we can represent on the main thread so
that if we do encounter range errors in future, they should not differ between
the main thread and the compositor).

This patch includes a crashtest. I have verified that, without the code changes
included in this patch, this crashtest fails on debug builds on OSX.

MozReview-Commit-ID: EDuKLzfEC0K
2017-05-02 16:49:51 +09:00

575 lines
22 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AnimationHelper.h"
#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode
#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
#include "mozilla/dom/KeyframeEffectReadOnly.h" // for dom::KeyFrameEffectReadOnly
#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction
#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
#include "nsDisplayList.h" // for nsDisplayTransform, etc
namespace mozilla {
namespace layers {
struct StyleAnimationValueCompositePair {
StyleAnimationValue mValue;
dom::CompositeOperation mComposite;
};
void
CompositorAnimationStorage::Clear()
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
mAnimatedValues.Clear();
mAnimations.Clear();
}
void
CompositorAnimationStorage::ClearById(const uint64_t& aId)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
mAnimatedValues.Remove(aId);
mAnimations.Remove(aId);
}
AnimatedValue*
CompositorAnimationStorage::GetAnimatedValue(const uint64_t& aId) const
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
return mAnimatedValues.Get(aId);
}
void
CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
gfx::Matrix4x4&& aTransformInDevSpace,
gfx::Matrix4x4&& aFrameTransform,
const TransformData& aData)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
AnimatedValue* value = new AnimatedValue(Move(aTransformInDevSpace), Move(aFrameTransform), aData);
mAnimatedValues.Put(aId, value);
}
void
CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
const float& aOpacity)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
AnimatedValue* value = new AnimatedValue(aOpacity);
mAnimatedValues.Put(aId, value);
}
AnimationArray*
CompositorAnimationStorage::GetAnimations(const uint64_t& aId) const
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
return mAnimations.Get(aId);
}
void
CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aValue)
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
AnimationArray* value = new AnimationArray(aValue);
mAnimations.Put(aId, value);
}
static StyleAnimationValue
SampleValue(float aPortion, const layers::Animation& aAnimation,
const StyleAnimationValueCompositePair& aStart,
const StyleAnimationValueCompositePair& aEnd,
const StyleAnimationValue& aLastValue,
uint64_t aCurrentIteration,
const StyleAnimationValue& aUnderlyingValue)
{
NS_ASSERTION(aStart.mValue.IsNull() || aEnd.mValue.IsNull() ||
aStart.mValue.GetUnit() == aEnd.mValue.GetUnit(),
"Must have same unit");
StyleAnimationValue startValue =
dom::KeyframeEffectReadOnly::CompositeValue(aAnimation.property(),
aStart.mValue,
aUnderlyingValue,
aStart.mComposite);
StyleAnimationValue endValue =
dom::KeyframeEffectReadOnly::CompositeValue(aAnimation.property(),
aEnd.mValue,
aUnderlyingValue,
aEnd.mComposite);
// Iteration composition for accumulate
if (static_cast<dom::IterationCompositeOperation>
(aAnimation.iterationComposite()) ==
dom::IterationCompositeOperation::Accumulate &&
aCurrentIteration > 0) {
// FIXME: Bug 1293492: Add a utility function to calculate both of
// below StyleAnimationValues.
startValue =
StyleAnimationValue::Accumulate(aAnimation.property(),
aLastValue.IsNull()
? aUnderlyingValue
: aLastValue,
Move(startValue),
aCurrentIteration);
endValue =
StyleAnimationValue::Accumulate(aAnimation.property(),
aLastValue.IsNull()
? aUnderlyingValue
: aLastValue,
Move(endValue),
aCurrentIteration);
}
StyleAnimationValue interpolatedValue;
// This should never fail because we only pass transform and opacity values
// to the compositor and they should never fail to interpolate.
DebugOnly<bool> uncomputeResult =
StyleAnimationValue::Interpolate(aAnimation.property(),
startValue, endValue,
aPortion, interpolatedValue);
MOZ_ASSERT(uncomputeResult, "could not uncompute value");
return interpolatedValue;
}
bool
AnimationHelper::SampleAnimationForEachNode(TimeStamp aTime,
AnimationArray& aAnimations,
InfallibleTArray<AnimData>& aAnimationData,
StyleAnimationValue& aAnimationValue,
bool& aHasInEffectAnimations)
{
bool activeAnimations = false;
if (aAnimations.IsEmpty()) {
return activeAnimations;
}
// Process in order, since later aAnimations override earlier ones.
for (size_t i = 0, iEnd = aAnimations.Length(); i < iEnd; ++i) {
Animation& animation = aAnimations[i];
AnimData& animData = aAnimationData[i];
activeAnimations = true;
MOZ_ASSERT((!animation.originTime().IsNull() &&
animation.startTime().type() != MaybeTimeDuration::Tnull_t) ||
animation.isNotPlaying(),
"If we are playing, we should have an origin time and a start"
" time");
// If the animation is not currently playing , e.g. paused or
// finished, then use the hold time to stay at the same position.
TimeDuration elapsedDuration = animation.isNotPlaying()
? animation.holdTime()
: (aTime - animation.originTime() -
animation.startTime().get_TimeDuration())
.MultDouble(animation.playbackRate());
TimingParams timing;
timing.mDuration.emplace(animation.duration());
timing.mDelay = animation.delay();
timing.mEndDelay = animation.endDelay();
timing.mIterations = animation.iterations();
timing.mIterationStart = animation.iterationStart();
timing.mDirection =
static_cast<dom::PlaybackDirection>(animation.direction());
timing.mFill = static_cast<dom::FillMode>(animation.fillMode());
timing.mFunction =
AnimationUtils::TimingFunctionToComputedTimingFunction(
animation.easingFunction());
ComputedTiming computedTiming =
dom::AnimationEffectReadOnly::GetComputedTimingAt(
Nullable<TimeDuration>(elapsedDuration), timing,
animation.playbackRate());
if (computedTiming.mProgress.IsNull()) {
continue;
}
uint32_t segmentIndex = 0;
size_t segmentSize = animation.segments().Length();
AnimationSegment* segment = animation.segments().Elements();
while (segment->endPortion() < computedTiming.mProgress.Value() &&
segmentIndex < segmentSize - 1) {
++segment;
++segmentIndex;
}
double positionInSegment =
(computedTiming.mProgress.Value() - segment->startPortion()) /
(segment->endPortion() - segment->startPortion());
double portion =
ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex],
positionInSegment,
computedTiming.mBeforeFlag);
StyleAnimationValueCompositePair from {
animData.mStartValues[segmentIndex],
static_cast<dom::CompositeOperation>(segment->startComposite())
};
StyleAnimationValueCompositePair to {
animData.mEndValues[segmentIndex],
static_cast<dom::CompositeOperation>(segment->endComposite())
};
// interpolate the property
aAnimationValue = SampleValue(portion,
animation,
from, to,
animData.mEndValues.LastElement(),
computedTiming.mCurrentIteration,
aAnimationValue);
aHasInEffectAnimations = true;
}
#ifdef DEBUG
// Sanity check that all of animation data are the same.
const AnimationData& lastData = aAnimations.LastElement().data();
for (const Animation& animation : aAnimations) {
const AnimationData& data = animation.data();
MOZ_ASSERT(data.type() == lastData.type(),
"The type of AnimationData should be the same");
if (data.type() == AnimationData::Tnull_t) {
continue;
}
MOZ_ASSERT(data.type() == AnimationData::TTransformData);
const TransformData& transformData = data.get_TransformData();
const TransformData& lastTransformData = lastData.get_TransformData();
MOZ_ASSERT(transformData.origin() == lastTransformData.origin() &&
transformData.transformOrigin() ==
lastTransformData.transformOrigin() &&
transformData.bounds() == lastTransformData.bounds() &&
transformData.appUnitsPerDevPixel() ==
lastTransformData.appUnitsPerDevPixel(),
"All of members of TransformData should be the same");
}
#endif
return activeAnimations;
}
static inline void
SetCSSAngle(const CSSAngle& aAngle, nsCSSValue& aValue)
{
aValue.SetFloatValue(aAngle.value(), nsCSSUnit(aAngle.unit()));
}
static nsCSSValueSharedList*
CreateCSSValueList(const InfallibleTArray<TransformFunction>& aFunctions)
{
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList** resultTail = getter_Transfers(result);
for (uint32_t i = 0; i < aFunctions.Length(); i++) {
RefPtr<nsCSSValue::Array> arr;
switch (aFunctions[i].type()) {
case TransformFunction::TRotationX:
{
const CSSAngle& angle = aFunctions[i].get_RotationX().angle();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_rotatex,
resultTail);
SetCSSAngle(angle, arr->Item(1));
break;
}
case TransformFunction::TRotationY:
{
const CSSAngle& angle = aFunctions[i].get_RotationY().angle();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_rotatey,
resultTail);
SetCSSAngle(angle, arr->Item(1));
break;
}
case TransformFunction::TRotationZ:
{
const CSSAngle& angle = aFunctions[i].get_RotationZ().angle();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_rotatez,
resultTail);
SetCSSAngle(angle, arr->Item(1));
break;
}
case TransformFunction::TRotation:
{
const CSSAngle& angle = aFunctions[i].get_Rotation().angle();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_rotate,
resultTail);
SetCSSAngle(angle, arr->Item(1));
break;
}
case TransformFunction::TRotation3D:
{
float x = aFunctions[i].get_Rotation3D().x();
float y = aFunctions[i].get_Rotation3D().y();
float z = aFunctions[i].get_Rotation3D().z();
const CSSAngle& angle = aFunctions[i].get_Rotation3D().angle();
arr =
StyleAnimationValue::AppendTransformFunction(eCSSKeyword_rotate3d,
resultTail);
arr->Item(1).SetFloatValue(x, eCSSUnit_Number);
arr->Item(2).SetFloatValue(y, eCSSUnit_Number);
arr->Item(3).SetFloatValue(z, eCSSUnit_Number);
SetCSSAngle(angle, arr->Item(4));
break;
}
case TransformFunction::TScale:
{
arr =
StyleAnimationValue::AppendTransformFunction(eCSSKeyword_scale3d,
resultTail);
arr->Item(1).SetFloatValue(aFunctions[i].get_Scale().x(), eCSSUnit_Number);
arr->Item(2).SetFloatValue(aFunctions[i].get_Scale().y(), eCSSUnit_Number);
arr->Item(3).SetFloatValue(aFunctions[i].get_Scale().z(), eCSSUnit_Number);
break;
}
case TransformFunction::TTranslation:
{
arr =
StyleAnimationValue::AppendTransformFunction(eCSSKeyword_translate3d,
resultTail);
arr->Item(1).SetFloatValue(aFunctions[i].get_Translation().x(), eCSSUnit_Pixel);
arr->Item(2).SetFloatValue(aFunctions[i].get_Translation().y(), eCSSUnit_Pixel);
arr->Item(3).SetFloatValue(aFunctions[i].get_Translation().z(), eCSSUnit_Pixel);
break;
}
case TransformFunction::TSkewX:
{
const CSSAngle& x = aFunctions[i].get_SkewX().x();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_skewx,
resultTail);
SetCSSAngle(x, arr->Item(1));
break;
}
case TransformFunction::TSkewY:
{
const CSSAngle& y = aFunctions[i].get_SkewY().y();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_skewy,
resultTail);
SetCSSAngle(y, arr->Item(1));
break;
}
case TransformFunction::TSkew:
{
const CSSAngle& x = aFunctions[i].get_Skew().x();
const CSSAngle& y = aFunctions[i].get_Skew().y();
arr = StyleAnimationValue::AppendTransformFunction(eCSSKeyword_skew,
resultTail);
SetCSSAngle(x, arr->Item(1));
SetCSSAngle(y, arr->Item(2));
break;
}
case TransformFunction::TTransformMatrix:
{
arr =
StyleAnimationValue::AppendTransformFunction(eCSSKeyword_matrix3d,
resultTail);
const gfx::Matrix4x4& matrix = aFunctions[i].get_TransformMatrix().value();
arr->Item(1).SetFloatValue(matrix._11, eCSSUnit_Number);
arr->Item(2).SetFloatValue(matrix._12, eCSSUnit_Number);
arr->Item(3).SetFloatValue(matrix._13, eCSSUnit_Number);
arr->Item(4).SetFloatValue(matrix._14, eCSSUnit_Number);
arr->Item(5).SetFloatValue(matrix._21, eCSSUnit_Number);
arr->Item(6).SetFloatValue(matrix._22, eCSSUnit_Number);
arr->Item(7).SetFloatValue(matrix._23, eCSSUnit_Number);
arr->Item(8).SetFloatValue(matrix._24, eCSSUnit_Number);
arr->Item(9).SetFloatValue(matrix._31, eCSSUnit_Number);
arr->Item(10).SetFloatValue(matrix._32, eCSSUnit_Number);
arr->Item(11).SetFloatValue(matrix._33, eCSSUnit_Number);
arr->Item(12).SetFloatValue(matrix._34, eCSSUnit_Number);
arr->Item(13).SetFloatValue(matrix._41, eCSSUnit_Number);
arr->Item(14).SetFloatValue(matrix._42, eCSSUnit_Number);
arr->Item(15).SetFloatValue(matrix._43, eCSSUnit_Number);
arr->Item(16).SetFloatValue(matrix._44, eCSSUnit_Number);
break;
}
case TransformFunction::TPerspective:
{
float perspective = aFunctions[i].get_Perspective().value();
arr =
StyleAnimationValue::AppendTransformFunction(eCSSKeyword_perspective,
resultTail);
arr->Item(1).SetFloatValue(perspective, eCSSUnit_Pixel);
break;
}
default:
NS_ASSERTION(false, "All functions should be implemented?");
}
}
if (aFunctions.Length() == 0) {
result = new nsCSSValueList();
result->mValue.SetNoneValue();
}
return new nsCSSValueSharedList(result.forget());
}
static StyleAnimationValue
ToStyleAnimationValue(const Animatable& aAnimatable)
{
StyleAnimationValue result;
switch (aAnimatable.type()) {
case Animatable::Tnull_t:
break;
case Animatable::TArrayOfTransformFunction: {
const InfallibleTArray<TransformFunction>& transforms =
aAnimatable.get_ArrayOfTransformFunction();
result.SetTransformValue(CreateCSSValueList(transforms));
break;
}
case Animatable::Tfloat:
result.SetFloatValue(aAnimatable.get_float());
break;
default:
MOZ_ASSERT_UNREACHABLE("Unsupported type");
}
return result;
}
void
AnimationHelper::SetAnimations(AnimationArray& aAnimations,
InfallibleTArray<AnimData>& aAnimData,
StyleAnimationValue& aBaseAnimationStyle)
{
for (uint32_t i = 0; i < aAnimations.Length(); i++) {
Animation& animation = aAnimations[i];
// Adjust fill mode to fill forwards so that if the main thread is delayed
// in clearing this animation we don't introduce flicker by jumping back to
// the old underlying value
switch (static_cast<dom::FillMode>(animation.fillMode())) {
case dom::FillMode::None:
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Forwards);
break;
case dom::FillMode::Backwards:
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Both);
break;
default:
break;
}
if (animation.baseStyle().type() != Animatable::Tnull_t) {
aBaseAnimationStyle = ToStyleAnimationValue(animation.baseStyle());
}
AnimData* data = aAnimData.AppendElement();
InfallibleTArray<Maybe<ComputedTimingFunction>>& functions =
data->mFunctions;
const InfallibleTArray<AnimationSegment>& segments = animation.segments();
for (uint32_t j = 0; j < segments.Length(); j++) {
TimingFunction tf = segments.ElementAt(j).sampleFn();
Maybe<ComputedTimingFunction> ctf =
AnimationUtils::TimingFunctionToComputedTimingFunction(tf);
functions.AppendElement(ctf);
}
// Precompute the StyleAnimationValues that we need if this is a transform
// animation.
InfallibleTArray<StyleAnimationValue>& startValues = data->mStartValues;
InfallibleTArray<StyleAnimationValue>& endValues = data->mEndValues;
for (const AnimationSegment& segment : segments) {
startValues.AppendElement(ToStyleAnimationValue(segment.startState()));
endValues.AppendElement(ToStyleAnimationValue(segment.endState()));
}
}
}
uint64_t
AnimationHelper::GetNextCompositorAnimationsId()
{
static uint32_t sNextId = 0;
++sNextId;
uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId());
uint64_t nextId = procId;
nextId = nextId << 32 | sNextId;
return nextId;
}
void
AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
TimeStamp aTime)
{
MOZ_ASSERT(aStorage);
// Do nothing if there are no compositor animations
if (!aStorage->AnimationsCount()) {
return;
}
//Sample the animations in CompositorAnimationStorage
for (auto iter = aStorage->ConstAnimationsTableIter();
!iter.Done(); iter.Next()) {
bool hasInEffectAnimations = false;
AnimationArray* animations = iter.UserData();
StyleAnimationValue animationValue;
InfallibleTArray<AnimData> animationData;
AnimationHelper::SetAnimations(*animations,
animationData,
animationValue);
AnimationHelper::SampleAnimationForEachNode(aTime,
*animations,
animationData,
animationValue,
hasInEffectAnimations);
if (!hasInEffectAnimations) {
continue;
}
// Store the AnimatedValue
Animation& animation = animations->LastElement();
switch (animation.property()) {
case eCSSProperty_opacity: {
aStorage->SetAnimatedValue(iter.Key(),
animationValue.GetFloatValue());
break;
}
case eCSSProperty_transform: {
nsCSSValueSharedList* list = animationValue.GetCSSValueSharedListValue();
const TransformData& transformData = animation.data().get_TransformData();
nsPoint origin = transformData.origin();
// we expect all our transform data to arrive in device pixels
gfx::Point3D transformOrigin = transformData.transformOrigin();
nsDisplayTransform::FrameTransformProperties props(list,
transformOrigin);
gfx::Matrix4x4 transform =
nsDisplayTransform::GetResultingTransformMatrix(props, origin,
transformData.appUnitsPerDevPixel(),
0, &transformData.bounds());
gfx::Matrix4x4 frameTransform = transform;
//TODO how do we support this without layer information
// If our parent layer is a perspective layer, then the offset into reference
// frame coordinates is already on that layer. If not, then we need to ask
// for it to be added here.
// if (!aLayer->GetParent() ||
// !aLayer->GetParent()->GetTransformIsPerspective()) {
// nsLayoutUtils::PostTranslate(transform, origin,
// transformData.appUnitsPerDevPixel(),
// true);
// }
// if (ContainerLayer* c = aLayer->AsContainerLayer()) {
// transform.PostScale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1);
// }
aStorage->SetAnimatedValue(iter.Key(),
Move(transform), Move(frameTransform),
transformData);
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
}
}
}
} // namespace layers
} // namespace mozilla