blob: 929f07371652f91d34d756c9b3b89042659fbb3e [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/animation/CompositorAnimations.h"
#include "core/animation/AnimatableDouble.h"
#include "core/animation/AnimatableFilterOperations.h"
#include "core/animation/AnimatableTransform.h"
#include "core/animation/AnimatableValue.h"
#include "core/animation/CompositorAnimationsImpl.h"
#include "core/platform/animation/AnimationTranslationUtil.h"
#include "core/rendering/CompositedLayerMapping.h"
#include "core/rendering/RenderBoxModelObject.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/RenderObject.h"
#include "public/platform/Platform.h"
#include "public/platform/WebAnimation.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebFilterAnimationCurve.h"
#include "public/platform/WebFilterKeyframe.h"
#include "public/platform/WebFloatAnimationCurve.h"
#include "public/platform/WebFloatKeyframe.h"
#include "public/platform/WebTransformAnimationCurve.h"
#include "public/platform/WebTransformKeyframe.h"
#include <algorithm>
#include <cmath>
namespace WebCore {
namespace {
void getKeyframeValuesForProperty(const KeyframeAnimationEffect* effect, CSSPropertyID id, double scale, bool reverse, KeyframeVector& values)
{
ASSERT(values.isEmpty());
const KeyframeVector& group = effect->getPropertySpecificKeyframes(id);
if (reverse) {
for (size_t i = group.size(); i--;) {
double offset = (1 - group[i]->offset()) * scale;
values.append(group[i]->cloneWithOffset(offset));
}
} else {
for (size_t i = 0; i < group.size(); ++i) {
double offset = group[i]->offset() * scale;
values.append(group[i]->cloneWithOffset(offset));
}
}
}
}
// -----------------------------------------------------------------------
// TimingFunctionReverser methods
// -----------------------------------------------------------------------
PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const LinearTimingFunction* timefunc)
{
return const_cast<LinearTimingFunction*>(timefunc);
}
PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const CubicBezierTimingFunction* timefunc)
{
switch (timefunc->subType()) {
case CubicBezierTimingFunction::EaseIn:
return CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseOut);
case CubicBezierTimingFunction::EaseOut:
return CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseIn);
case CubicBezierTimingFunction::EaseInOut:
return const_cast<CubicBezierTimingFunction*>(timefunc);
case CubicBezierTimingFunction::Ease: // Ease is not symmetrical
case CubicBezierTimingFunction::Custom:
return CubicBezierTimingFunction::create(1 - timefunc->x2(), 1 - timefunc->y2(), 1 - timefunc->x1(), 1 - timefunc->y1());
default:
ASSERT_NOT_REACHED();
return PassRefPtr<TimingFunction>();
}
}
PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const ChainedTimingFunction* timefunc)
{
RefPtr<ChainedTimingFunction> reversed = ChainedTimingFunction::create();
for (size_t i = 0; i < timefunc->m_segments.size(); i++) {
size_t index = timefunc->m_segments.size() - i - 1;
RefPtr<TimingFunction> rtf = reverse(timefunc->m_segments[index].m_timingFunction.get());
reversed->appendSegment(1 - timefunc->m_segments[index].m_min, rtf.get());
}
return reversed;
}
PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const TimingFunction* timefunc)
{
switch (timefunc->type()) {
case TimingFunction::LinearFunction: {
const LinearTimingFunction* linear = toLinearTimingFunction(timefunc);
return reverse(linear);
}
case TimingFunction::CubicBezierFunction: {
const CubicBezierTimingFunction* cubic = toCubicBezierTimingFunction(timefunc);
return reverse(cubic);
}
case TimingFunction::ChainedFunction: {
const ChainedTimingFunction* chained = toChainedTimingFunction(timefunc);
return reverse(chained);
}
// Steps function can not be reversed.
case TimingFunction::StepsFunction:
default:
ASSERT_NOT_REACHED();
return PassRefPtr<TimingFunction>();
}
}
// -----------------------------------------------------------------------
// CompositorAnimations public API
// -----------------------------------------------------------------------
bool CompositorAnimations::isCandidateForAnimationOnCompositor(const Timing& timing, const AnimationEffect& effect)
{
const KeyframeAnimationEffect& keyframeEffect = *toKeyframeAnimationEffect(&effect);
// Are the keyframes convertible?
const KeyframeAnimationEffect::KeyframeVector frames = keyframeEffect.getFrames();
for (size_t i = 0; i < frames.size(); ++i) {
// Only replace mode can be accelerated
if (frames[i]->composite() != AnimationEffect::CompositeReplace)
return false;
// Check all the properties can be accelerated
const PropertySet properties = frames[i]->properties(); // FIXME: properties creates a whole new PropertySet!
if (properties.isEmpty())
return false;
for (PropertySet::const_iterator it = properties.begin(); it != properties.end(); ++it) {
switch (*it) {
case CSSPropertyOpacity:
continue;
case CSSPropertyWebkitTransform:
if (toAnimatableTransform(frames[i]->propertyValue(CSSPropertyWebkitTransform))->transformOperations().dependsOnBoxSize())
return false;
continue;
case CSSPropertyWebkitFilter: {
const FilterOperations& operations = toAnimatableFilterOperations(frames[i]->propertyValue(CSSPropertyWebkitFilter))->operations();
if (operations.hasFilterThatMovesPixels())
return false;
for (size_t i = 0; i < operations.size(); i++) {
const FilterOperation& op = *operations.at(i);
if (op.type() == FilterOperation::VALIDATED_CUSTOM || op.type() == FilterOperation::CUSTOM)
return false;
}
continue;
}
default:
return false;
}
}
}
// Is the timing object convertible?
CompositorAnimationsImpl::CompositorTiming out;
if (!CompositorAnimationsImpl::convertTimingForCompositor(timing, out))
return false;
// Is the timing function convertible?
switch (timing.timingFunction->type()) {
case TimingFunction::LinearFunction:
break;
case TimingFunction::CubicBezierFunction:
// Can have a cubic if we don't have to split it (IE only have two frames).
if (frames.size() != 2)
return false;
ASSERT(frames[0]->offset() == 0.0 && frames[1]->offset() == 1.0);
break;
case TimingFunction::StepsFunction:
return false;
case TimingFunction::ChainedFunction: {
// Currently we only support chained segments in the form the CSS code
// generates. These chained segments are only one level deep and have
// one timing function per frame.
const ChainedTimingFunction* chained = static_cast<const ChainedTimingFunction*>(timing.timingFunction.get());
if (!chained->m_segments.size())
return false;
if (frames.size() != chained->m_segments.size() + 1)
return false;
for (size_t timeIndex = 0; timeIndex < chained->m_segments.size(); timeIndex++) {
const ChainedTimingFunction::Segment& segment = chained->m_segments[timeIndex];
if (frames[timeIndex]->offset() != segment.m_min || frames[timeIndex + 1]->offset() != segment.m_max)
return false;
switch (segment.m_timingFunction->type()) {
case TimingFunction::LinearFunction:
case TimingFunction::CubicBezierFunction:
continue;
case TimingFunction::StepsFunction:
case TimingFunction::ChainedFunction:
default:
return false;
}
}
break;
}
default:
ASSERT_NOT_REACHED();
return false;
}
return true;
}
bool CompositorAnimations::canStartAnimationOnCompositor(const Element& element)
{
return element.renderer() && element.renderer()->compositingState() == PaintsIntoOwnBacking;
}
bool CompositorAnimations::startAnimationOnCompositor(const Element& element, const Timing& timing, const AnimationEffect& effect, Vector<int>& startedAnimationIds)
{
ASSERT(startedAnimationIds.isEmpty());
ASSERT(isCandidateForAnimationOnCompositor(timing, effect));
ASSERT(canStartAnimationOnCompositor(element));
const KeyframeAnimationEffect& keyframeEffect = *toKeyframeAnimationEffect(&effect);
RenderLayer* layer = toRenderBoxModelObject(element.renderer())->layer();
ASSERT(layer);
Vector<OwnPtr<blink::WebAnimation> > animations;
CompositorAnimationsImpl::getAnimationOnCompositor(timing, keyframeEffect, animations);
ASSERT(!animations.isEmpty());
for (size_t i = 0; i < animations.size(); ++i) {
int id = animations[i]->id();
if (!layer->compositedLayerMapping()->mainGraphicsLayer()->addAnimation(animations[i].release())) {
// FIXME: We should know ahead of time whether these animations can be started.
for (size_t j = 0; j < startedAnimationIds.size(); ++j)
cancelAnimationOnCompositor(element, startedAnimationIds[j]);
startedAnimationIds.clear();
return false;
}
startedAnimationIds.append(id);
}
ASSERT(!startedAnimationIds.isEmpty());
return true;
}
void CompositorAnimations::cancelAnimationOnCompositor(const Element& element, int id)
{
if (!canStartAnimationOnCompositor(element)) {
ASSERT_NOT_REACHED();
return;
}
toRenderBoxModelObject(element.renderer())->layer()->compositedLayerMapping()->mainGraphicsLayer()->removeAnimation(id);
}
void CompositorAnimations::pauseAnimationForTestingOnCompositor(const Element& element, int id, double pauseTime)
{
if (!canStartAnimationOnCompositor(element)) {
ASSERT_NOT_REACHED();
return;
}
toRenderBoxModelObject(element.renderer())->layer()->compositedLayerMapping()->mainGraphicsLayer()->pauseAnimation(id, pauseTime);
}
// -----------------------------------------------------------------------
// CompositorAnimationsImpl
// -----------------------------------------------------------------------
bool CompositorAnimationsImpl::convertTimingForCompositor(const Timing& timing, CompositorTiming& out)
{
timing.assertValid();
// All fill modes are supported (the calling code handles them).
// FIXME: Support non-zero iteration start.
if (timing.iterationStart)
return false;
// FIXME: Compositor only supports positive, integer iteration counts.
// Zero iterations could be converted, but silly.
if ((std::floor(timing.iterationCount) != timing.iterationCount) || timing.iterationCount <= 0)
return false;
if (!timing.iterationDuration)
return false;
// FIXME: Support other playback rates
if (timing.playbackRate != 1)
return false;
// All directions are supported.
// Now attempt an actual conversion
out.scaledDuration = timing.iterationDuration;
ASSERT(out.scaledDuration > 0);
double scaledStartDelay = timing.startDelay;
if (scaledStartDelay > 0 && scaledStartDelay > out.scaledDuration * timing.iterationCount)
return false;
out.reverse = (timing.direction == Timing::PlaybackDirectionReverse
|| timing.direction == Timing::PlaybackDirectionAlternateReverse);
out.alternate = (timing.direction == Timing::PlaybackDirectionAlternate
|| timing.direction == Timing::PlaybackDirectionAlternateReverse);
if (!std::isfinite(timing.iterationCount)) {
out.adjustedIterationCount = -1;
} else {
out.adjustedIterationCount = std::floor(timing.iterationCount);
ASSERT(out.adjustedIterationCount > 0);
}
// Compositor's time offset is positive for seeking into the animation.
out.scaledTimeOffset = -scaledStartDelay;
return true;
}
namespace {
template<typename PlatformAnimationCurveType, typename PlatformAnimationKeyframeType>
void addKeyframeWithTimingFunction(PlatformAnimationCurveType& curve, const PlatformAnimationKeyframeType& keyframe, const TimingFunction* timingFunction)
{
if (!timingFunction) {
curve.add(keyframe);
return;
}
switch (timingFunction->type()) {
case TimingFunction::LinearFunction:
curve.add(keyframe, blink::WebAnimationCurve::TimingFunctionTypeLinear);
return;
case TimingFunction::CubicBezierFunction: {
const CubicBezierTimingFunction* cubic = toCubicBezierTimingFunction(timingFunction);
if (cubic->subType() == CubicBezierTimingFunction::Custom) {
curve.add(keyframe, cubic->x1(), cubic->y1(), cubic->x2(), cubic->y2());
} else {
blink::WebAnimationCurve::TimingFunctionType easeType;
switch (cubic->subType()) {
case CubicBezierTimingFunction::Ease:
easeType = blink::WebAnimationCurve::TimingFunctionTypeEase;
break;
case CubicBezierTimingFunction::EaseIn:
easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseIn;
break;
case CubicBezierTimingFunction::EaseOut:
easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseOut;
break;
case CubicBezierTimingFunction::EaseInOut:
easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseInOut;
break;
// Custom Bezier are handled seperately.
case CubicBezierTimingFunction::Custom:
default:
ASSERT_NOT_REACHED();
return;
}
curve.add(keyframe, easeType);
}
return;
}
case TimingFunction::StepsFunction:
case TimingFunction::ChainedFunction:
default:
ASSERT_NOT_REACHED();
return;
}
}
} // namespace anoymous
void CompositorAnimationsImpl::addKeyframesToCurve(blink::WebAnimationCurve& curve, const KeyframeVector& keyframes, const TimingFunction& timingFunction)
{
for (size_t i = 0; i < keyframes.size(); i++) {
const TimingFunction* keyframeTimingFunction = 0;
if (i + 1 < keyframes.size()) { // Last keyframe has no timing function
switch (timingFunction.type()) {
case TimingFunction::LinearFunction:
case TimingFunction::CubicBezierFunction:
keyframeTimingFunction = &timingFunction;
break;
case TimingFunction::ChainedFunction: {
const ChainedTimingFunction& chained = toChainedTimingFunction(timingFunction);
// ChainedTimingFunction criteria was checked in isCandidate,
// assert it is valid.
ASSERT(keyframes.size() == chained.m_segments.size() + 1);
keyframeTimingFunction = chained.m_segments[i].m_timingFunction.get();
break;
}
case TimingFunction::StepsFunction:
default:
ASSERT_NOT_REACHED();
}
}
ASSERT(!keyframes[i]->value()->dependsOnUnderlyingValue());
RefPtr<AnimatableValue> value = keyframes[i]->value()->compositeOnto(0);
switch (curve.type()) {
case blink::WebAnimationCurve::AnimationCurveTypeFilter: {
OwnPtr<blink::WebFilterOperations> ops = adoptPtr(blink::Platform::current()->compositorSupport()->createFilterOperations());
bool converted = toWebFilterOperations(toAnimatableFilterOperations(value.get())->operations(), ops.get());
ASSERT_UNUSED(converted, converted);
blink::WebFilterKeyframe filterKeyframe(keyframes[i]->offset(), ops.release());
blink::WebFilterAnimationCurve* filterCurve = static_cast<blink::WebFilterAnimationCurve*>(&curve);
addKeyframeWithTimingFunction(*filterCurve, filterKeyframe, keyframeTimingFunction);
break;
}
case blink::WebAnimationCurve::AnimationCurveTypeFloat: {
blink::WebFloatKeyframe floatKeyframe(keyframes[i]->offset(), toAnimatableDouble(value.get())->toDouble());
blink::WebFloatAnimationCurve* floatCurve = static_cast<blink::WebFloatAnimationCurve*>(&curve);
addKeyframeWithTimingFunction(*floatCurve, floatKeyframe, keyframeTimingFunction);
break;
}
case blink::WebAnimationCurve::AnimationCurveTypeTransform: {
OwnPtr<blink::WebTransformOperations> ops = adoptPtr(blink::Platform::current()->compositorSupport()->createTransformOperations());
toWebTransformOperations(toAnimatableTransform(value.get())->transformOperations(), FloatSize(), ops.get());
blink::WebTransformKeyframe transformKeyframe(keyframes[i]->offset(), ops.release());
blink::WebTransformAnimationCurve* transformCurve = static_cast<blink::WebTransformAnimationCurve*>(&curve);
addKeyframeWithTimingFunction(*transformCurve, transformKeyframe, keyframeTimingFunction);
break;
}
default:
ASSERT_NOT_REACHED();
}
}
}
void CompositorAnimationsImpl::getAnimationOnCompositor(
const Timing& timing, const KeyframeAnimationEffect& effect, Vector<OwnPtr<blink::WebAnimation> >& animations)
{
ASSERT(animations.isEmpty());
CompositorTiming compositorTiming;
bool timingValid = convertTimingForCompositor(timing, compositorTiming);
ASSERT_UNUSED(timingValid, timingValid);
RefPtr<TimingFunction> timingFunction = timing.timingFunction;
if (compositorTiming.reverse)
timingFunction = CompositorAnimationsTimingFunctionReverser::reverse(timingFunction.get());
PropertySet properties = effect.properties();
ASSERT(!properties.isEmpty());
for (PropertySet::iterator it = properties.begin(); it != properties.end(); ++it) {
KeyframeVector values;
getKeyframeValuesForProperty(&effect, *it, compositorTiming.scaledDuration, compositorTiming.reverse, values);
blink::WebAnimation::TargetProperty targetProperty;
OwnPtr<blink::WebAnimationCurve> curve;
switch (*it) {
case CSSPropertyOpacity: {
targetProperty = blink::WebAnimation::TargetPropertyOpacity;
blink::WebFloatAnimationCurve* floatCurve = blink::Platform::current()->compositorSupport()->createFloatAnimationCurve();
addKeyframesToCurve(*floatCurve, values, *timingFunction.get());
curve = adoptPtr(floatCurve);
break;
}
case CSSPropertyWebkitFilter: {
targetProperty = blink::WebAnimation::TargetPropertyFilter;
blink::WebFilterAnimationCurve* filterCurve = blink::Platform::current()->compositorSupport()->createFilterAnimationCurve();
addKeyframesToCurve(*filterCurve, values, *timingFunction);
curve = adoptPtr(filterCurve);
break;
}
case CSSPropertyWebkitTransform: {
targetProperty = blink::WebAnimation::TargetPropertyTransform;
blink::WebTransformAnimationCurve* transformCurve = blink::Platform::current()->compositorSupport()->createTransformAnimationCurve();
addKeyframesToCurve(*transformCurve, values, *timingFunction.get());
curve = adoptPtr(transformCurve);
break;
}
default:
ASSERT_NOT_REACHED();
continue;
}
ASSERT(curve.get());
OwnPtr<blink::WebAnimation> animation = adoptPtr(blink::Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty));
animation->setIterations(compositorTiming.adjustedIterationCount);
animation->setTimeOffset(compositorTiming.scaledTimeOffset);
animation->setAlternatesDirection(compositorTiming.alternate);
animations.append(animation.release());
}
ASSERT(!animations.isEmpty());
}
} // namespace WebCore