| /* |
| * Copyright (C) 2010 Apple Inc. All rights reserved. |
| * Copyright (C) 2011, 2012, 2013 Collabora Ltd. |
| * Copyright (C) 2012 Intel Corporation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE COMPUTER, INC. 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" |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| #include "GraphicsLayerClutter.h" |
| |
| #include "Animation.h" |
| #include "FloatConversion.h" |
| #include "FloatRect.h" |
| #include "GraphicsLayerActor.h" |
| #include "GraphicsLayerFactory.h" |
| #include "NotImplemented.h" |
| #include "RefPtrCairo.h" |
| #include "RotateTransformOperation.h" |
| #include "ScaleTransformOperation.h" |
| #include "TransformState.h" |
| #include "TranslateTransformOperation.h" |
| #include <limits.h> |
| #include <wtf/text/CString.h> |
| #include <wtf/text/WTFString.h> |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| // If we send a duration of 0 to ClutterTimeline, then it will fail to set the duration. |
| // So send a very small value instead. |
| static const float cAnimationAlmostZeroDuration = 1e-3f; |
| |
| static bool isTransformTypeTransformationMatrix(TransformOperation::OperationType transformType) |
| { |
| switch (transformType) { |
| case TransformOperation::SKEW_X: |
| case TransformOperation::SKEW_Y: |
| case TransformOperation::SKEW: |
| case TransformOperation::MATRIX: |
| case TransformOperation::ROTATE_3D: |
| case TransformOperation::MATRIX_3D: |
| case TransformOperation::PERSPECTIVE: |
| case TransformOperation::IDENTITY: |
| case TransformOperation::NONE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool isTransformTypeFloatPoint3D(TransformOperation::OperationType transformType) |
| { |
| switch (transformType) { |
| case TransformOperation::SCALE: |
| case TransformOperation::SCALE_3D: |
| case TransformOperation::TRANSLATE: |
| case TransformOperation::TRANSLATE_3D: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool isTransformTypeNumber(TransformOperation::OperationType transformType) |
| { |
| return !isTransformTypeTransformationMatrix(transformType) && !isTransformTypeFloatPoint3D(transformType); |
| } |
| |
| static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, float& value) |
| { |
| switch (transformType) { |
| case TransformOperation::ROTATE: |
| case TransformOperation::ROTATE_X: |
| case TransformOperation::ROTATE_Y: |
| value = transformOp ? narrowPrecisionToFloat(deg2rad(static_cast<const RotateTransformOperation*>(transformOp)->angle())) : 0; |
| break; |
| case TransformOperation::SCALE_X: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->x()) : 1; |
| break; |
| case TransformOperation::SCALE_Y: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->y()) : 1; |
| break; |
| case TransformOperation::SCALE_Z: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->z()) : 1; |
| break; |
| case TransformOperation::TRANSLATE_X: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->x(size)) : 0; |
| break; |
| case TransformOperation::TRANSLATE_Y: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->y(size)) : 0; |
| break; |
| case TransformOperation::TRANSLATE_Z: |
| value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->z(size)) : 0; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, FloatPoint3D& value) |
| { |
| switch (transformType) { |
| case TransformOperation::SCALE: |
| case TransformOperation::SCALE_3D: |
| value.setX(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->x()) : 1); |
| value.setY(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->y()) : 1); |
| value.setZ(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->z()) : 1); |
| break; |
| case TransformOperation::TRANSLATE: |
| case TransformOperation::TRANSLATE_3D: |
| value.setX(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->x(size)) : 0); |
| value.setY(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->y(size)) : 0); |
| value.setZ(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->z(size)) : 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, TransformationMatrix& value) |
| { |
| switch (transformType) { |
| case TransformOperation::SKEW_X: |
| case TransformOperation::SKEW_Y: |
| case TransformOperation::SKEW: |
| case TransformOperation::MATRIX: |
| case TransformOperation::ROTATE_3D: |
| case TransformOperation::MATRIX_3D: |
| case TransformOperation::PERSPECTIVE: |
| case TransformOperation::IDENTITY: |
| case TransformOperation::NONE: |
| if (transformOp) |
| transformOp->apply(value, size); |
| else |
| value.makeIdentity(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static PlatformClutterAnimation::ValueFunctionType getValueFunctionNameForTransformOperation(TransformOperation::OperationType transformType) |
| { |
| // Use literal strings to avoid link-time dependency on those symbols. |
| switch (transformType) { |
| case TransformOperation::ROTATE_X: |
| return PlatformClutterAnimation::RotateX; |
| case TransformOperation::ROTATE_Y: |
| return PlatformClutterAnimation::RotateY; |
| case TransformOperation::ROTATE: |
| return PlatformClutterAnimation::RotateZ; |
| case TransformOperation::SCALE_X: |
| return PlatformClutterAnimation::ScaleX; |
| case TransformOperation::SCALE_Y: |
| return PlatformClutterAnimation::ScaleY; |
| case TransformOperation::SCALE_Z: |
| return PlatformClutterAnimation::ScaleZ; |
| case TransformOperation::TRANSLATE_X: |
| return PlatformClutterAnimation::TranslateX; |
| case TransformOperation::TRANSLATE_Y: |
| return PlatformClutterAnimation::TranslateY; |
| case TransformOperation::TRANSLATE_Z: |
| return PlatformClutterAnimation::TranslateZ; |
| case TransformOperation::SCALE: |
| case TransformOperation::SCALE_3D: |
| return PlatformClutterAnimation::Scale; |
| case TransformOperation::TRANSLATE: |
| case TransformOperation::TRANSLATE_3D: |
| return PlatformClutterAnimation::Translate; |
| case TransformOperation::MATRIX_3D: |
| return PlatformClutterAnimation::Matrix; |
| default: |
| return PlatformClutterAnimation::NoValueFunction; |
| } |
| } |
| |
| static String propertyIdToString(AnimatedPropertyID property) |
| { |
| switch (property) { |
| case AnimatedPropertyWebkitTransform: |
| return "transform"; |
| case AnimatedPropertyOpacity: |
| return "opacity"; |
| case AnimatedPropertyBackgroundColor: |
| return "backgroundColor"; |
| case AnimatedPropertyWebkitFilter: |
| ASSERT_NOT_REACHED(); |
| case AnimatedPropertyInvalid: |
| ASSERT_NOT_REACHED(); |
| } |
| ASSERT_NOT_REACHED(); |
| return ""; |
| } |
| |
| static String animationIdentifier(const String& animationName, AnimatedPropertyID property, int index) |
| { |
| return animationName + '_' + String::number(property) + '_' + String::number(index); |
| } |
| |
| static bool animationHasStepsTimingFunction(const KeyframeValueList& valueList, const Animation* anim) |
| { |
| if (anim->timingFunction()->isStepsTimingFunction()) |
| return true; |
| |
| for (unsigned i = 0; i < valueList.size(); ++i) { |
| const TimingFunction* timingFunction = valueList.at(i)->timingFunction(); |
| if (timingFunction && timingFunction->isStepsTimingFunction()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // This is the hook for WebCore compositor to know that the webKit clutter port implements |
| // compositing with GraphicsLayerClutter. |
| PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerFactory* factory, GraphicsLayerClient* client) |
| { |
| if (!factory) |
| return adoptPtr(new GraphicsLayerClutter(client)); |
| |
| return factory->createGraphicsLayer(client); |
| } |
| |
| PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client) |
| { |
| return adoptPtr(new GraphicsLayerClutter(client)); |
| } |
| |
| GraphicsLayerClutter::GraphicsLayerClutter(GraphicsLayerClient* client) |
| : GraphicsLayer(client) |
| , m_uncommittedChanges(0) |
| { |
| // ClutterRectangle will be used to show the debug border. |
| m_layer = graphicsLayerActorNewWithClient(LayerTypeWebLayer, this); |
| } |
| |
| static gboolean idleDestroy(gpointer data) |
| { |
| GRefPtr<ClutterActor> actor = adoptGRef(CLUTTER_ACTOR(data)); |
| ClutterActor* parent = clutter_actor_get_parent(actor.get()); |
| |
| // We should remove child actors manually because the container of Clutter |
| // seems to have a bug to remove its child actors when it is removed. |
| if (GRAPHICS_LAYER_IS_ACTOR(GRAPHICS_LAYER_ACTOR(actor.get()))) |
| graphicsLayerActorRemoveAll(GRAPHICS_LAYER_ACTOR(actor.get())); |
| |
| if (parent) |
| clutter_actor_remove_child(parent, actor.get()); |
| |
| // FIXME: we should assert that the actor's ref count is 1 here, but some |
| // of them are getting here with 2! |
| // ASSERT((G_OBJECT(actor.get()))->ref_count == 1); |
| |
| return FALSE; |
| } |
| |
| GraphicsLayerClutter::~GraphicsLayerClutter() |
| { |
| if (graphicsLayerActorGetLayerType(m_layer.get()) == GraphicsLayerClutter::LayerTypeRootLayer) |
| return; |
| |
| // Even though we call notifyFlushRequired to remove existing animations in removeAnimation(), |
| // removeClutterAnimationFromLayer has been never reached since the root layer is destroyed. |
| // It means that we haven't lost a change to remove actual animations from clutterActor. |
| // So, we call explictly updateAnimations once here to remove uncommitted animations. |
| if (m_uncommittedChanges & AnimationChanged) |
| updateAnimations(); |
| |
| willBeDestroyed(); |
| |
| // We destroy the actors on an idle so that the main loop can run enough to |
| // repaint the background that will replace the actor. |
| if (m_layer) { |
| graphicsLayerActorSetClient(m_layer.get(), 0); |
| g_idle_add(idleDestroy, m_layer.leakRef()); |
| } |
| } |
| |
| void GraphicsLayerClutter::setName(const String& name) |
| { |
| String longName = String::format("Actor(%p) GraphicsLayer(%p) ", m_layer.get(), this) + name; |
| GraphicsLayer::setName(longName); |
| noteLayerPropertyChanged(NameChanged); |
| } |
| |
| ClutterActor* GraphicsLayerClutter::platformLayer() const |
| { |
| return CLUTTER_ACTOR(m_layer.get()); |
| } |
| |
| void GraphicsLayerClutter::setNeedsDisplay() |
| { |
| FloatRect hugeRect(FloatPoint(), m_size); |
| setNeedsDisplayInRect(hugeRect); |
| } |
| |
| void GraphicsLayerClutter::setNeedsDisplayInRect(const FloatRect& r) |
| { |
| if (!drawsContent()) |
| return; |
| |
| FloatRect rect(r); |
| FloatRect layerBounds(FloatPoint(), m_size); |
| rect.intersect(layerBounds); |
| if (rect.isEmpty()) |
| return; |
| |
| const size_t maxDirtyRects = 32; |
| |
| for (size_t i = 0; i < m_dirtyRects.size(); ++i) { |
| if (m_dirtyRects[i].contains(rect)) |
| return; |
| } |
| |
| if (m_dirtyRects.size() < maxDirtyRects) |
| m_dirtyRects.append(rect); |
| else |
| m_dirtyRects[0].unite(rect); |
| |
| noteLayerPropertyChanged(DirtyRectsChanged); |
| } |
| |
| void GraphicsLayerClutter::setAnchorPoint(const FloatPoint3D& point) |
| { |
| if (point == m_anchorPoint) |
| return; |
| |
| GraphicsLayer::setAnchorPoint(point); |
| noteLayerPropertyChanged(GeometryChanged); |
| } |
| |
| void GraphicsLayerClutter::setOpacity(float opacity) |
| { |
| float clampedOpacity = max(0.0f, min(opacity, 1.0f)); |
| if (clampedOpacity == m_opacity) |
| return; |
| |
| GraphicsLayer::setOpacity(clampedOpacity); |
| noteLayerPropertyChanged(OpacityChanged); |
| } |
| |
| void GraphicsLayerClutter::setPosition(const FloatPoint& point) |
| { |
| if (point == m_position) |
| return; |
| |
| GraphicsLayer::setPosition(point); |
| noteLayerPropertyChanged(GeometryChanged); |
| } |
| |
| void GraphicsLayerClutter::setSize(const FloatSize& size) |
| { |
| if (size == m_size) |
| return; |
| |
| GraphicsLayer::setSize(size); |
| noteLayerPropertyChanged(GeometryChanged); |
| } |
| void GraphicsLayerClutter::setTransform(const TransformationMatrix& t) |
| { |
| if (t == m_transform) |
| return; |
| |
| GraphicsLayer::setTransform(t); |
| noteLayerPropertyChanged(TransformChanged); |
| } |
| |
| void GraphicsLayerClutter::setDrawsContent(bool drawsContent) |
| { |
| if (drawsContent == m_drawsContent) |
| return; |
| |
| GraphicsLayer::setDrawsContent(drawsContent); |
| noteLayerPropertyChanged(DrawsContentChanged); |
| } |
| |
| void GraphicsLayerClutter::setParent(GraphicsLayer* childLayer) |
| { |
| notImplemented(); |
| |
| GraphicsLayer::setParent(childLayer); |
| } |
| |
| bool GraphicsLayerClutter::setChildren(const Vector<GraphicsLayer*>& children) |
| { |
| bool childrenChanged = GraphicsLayer::setChildren(children); |
| if (childrenChanged) |
| noteSublayersChanged(); |
| |
| return childrenChanged; |
| } |
| |
| void GraphicsLayerClutter::addChild(GraphicsLayer* childLayer) |
| { |
| GraphicsLayer::addChild(childLayer); |
| noteSublayersChanged(); |
| } |
| |
| void GraphicsLayerClutter::addChildAtIndex(GraphicsLayer* childLayer, int index) |
| { |
| GraphicsLayer::addChildAtIndex(childLayer, index); |
| noteSublayersChanged(); |
| } |
| |
| void GraphicsLayerClutter::addChildBelow(GraphicsLayer* childLayer, GraphicsLayer* sibling) |
| { |
| GraphicsLayer::addChildBelow(childLayer, sibling); |
| noteSublayersChanged(); |
| } |
| |
| void GraphicsLayerClutter::addChildAbove(GraphicsLayer* childLayer, GraphicsLayer* sibling) |
| { |
| GraphicsLayer::addChildAbove(childLayer, sibling); |
| noteSublayersChanged(); |
| } |
| |
| bool GraphicsLayerClutter::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild) |
| { |
| if (GraphicsLayer::replaceChild(oldChild, newChild)) { |
| noteSublayersChanged(); |
| return true; |
| } |
| return false; |
| } |
| |
| void GraphicsLayerClutter::removeFromParent() |
| { |
| if (m_parent) |
| static_cast<GraphicsLayerClutter*>(m_parent)->noteSublayersChanged(); |
| GraphicsLayer::removeFromParent(); |
| } |
| |
| void GraphicsLayerClutter::platformClutterLayerPaintContents(GraphicsContext& context, const IntRect& clip) |
| { |
| paintGraphicsLayerContents(context, clip); |
| } |
| |
| void GraphicsLayerClutter::platformClutterLayerAnimationStarted(double startTime) |
| { |
| if (m_client) |
| m_client->notifyAnimationStarted(this, startTime); |
| } |
| |
| void GraphicsLayerClutter::repaintLayerDirtyRects() |
| { |
| if (!m_dirtyRects.size()) |
| return; |
| |
| for (size_t i = 0; i < m_dirtyRects.size(); ++i) |
| graphicsLayerActorInvalidateRectangle(m_layer.get(), m_dirtyRects[i]); |
| |
| m_dirtyRects.clear(); |
| } |
| |
| void GraphicsLayerClutter::updateOpacityOnLayer() |
| { |
| clutter_actor_set_opacity(CLUTTER_ACTOR(primaryLayer()), static_cast<guint8>(roundf(m_opacity * 255))); |
| } |
| |
| void GraphicsLayerClutter::updateAnimations() |
| { |
| if (m_animationsToProcess.size()) { |
| AnimationsToProcessMap::const_iterator end = m_animationsToProcess.end(); |
| for (AnimationsToProcessMap::const_iterator it = m_animationsToProcess.begin(); it != end; ++it) { |
| const String& currAnimationName = it->key; |
| AnimationsMap::iterator animationIt = m_runningAnimations.find(currAnimationName); |
| if (animationIt == m_runningAnimations.end()) |
| continue; |
| |
| const AnimationProcessingAction& processingInfo = it->value; |
| const Vector<LayerPropertyAnimation>& animations = animationIt->value; |
| for (size_t i = 0; i < animations.size(); ++i) { |
| const LayerPropertyAnimation& currAnimation = animations[i]; |
| switch (processingInfo.action) { |
| case Remove: |
| removeClutterAnimationFromLayer(currAnimation.m_property, currAnimationName, currAnimation.m_index); |
| break; |
| case Pause: |
| pauseClutterAnimationOnLayer(currAnimation.m_property, currAnimationName, currAnimation.m_index, processingInfo.timeOffset); |
| break; |
| } |
| } |
| |
| if (processingInfo.action == Remove) |
| m_runningAnimations.remove(currAnimationName); |
| } |
| |
| m_animationsToProcess.clear(); |
| } |
| |
| size_t numAnimations; |
| if ((numAnimations = m_uncomittedAnimations.size())) { |
| for (size_t i = 0; i < numAnimations; ++i) { |
| const LayerPropertyAnimation& pendingAnimation = m_uncomittedAnimations[i]; |
| setAnimationOnLayer(pendingAnimation.m_animation.get(), pendingAnimation.m_property, pendingAnimation.m_name, pendingAnimation.m_index, pendingAnimation.m_timeOffset); |
| |
| AnimationsMap::iterator it = m_runningAnimations.find(pendingAnimation.m_name); |
| if (it == m_runningAnimations.end()) { |
| Vector<LayerPropertyAnimation> animations; |
| animations.append(pendingAnimation); |
| m_runningAnimations.add(pendingAnimation.m_name, animations); |
| } else { |
| Vector<LayerPropertyAnimation>& animations = it->value; |
| animations.append(pendingAnimation); |
| } |
| } |
| |
| m_uncomittedAnimations.clear(); |
| } |
| } |
| |
| FloatPoint GraphicsLayerClutter::computePositionRelativeToBase(float& pageScale) const |
| { |
| pageScale = 1; |
| |
| FloatPoint offset; |
| for (const GraphicsLayer* currLayer = this; currLayer; currLayer = currLayer->parent()) { |
| if (currLayer->appliesPageScale()) { |
| if (currLayer->client()) |
| pageScale = currLayer->pageScaleFactor(); |
| return offset; |
| } |
| |
| offset += currLayer->position(); |
| } |
| |
| return FloatPoint(); |
| } |
| |
| // called from void RenderLayerCompositor::flushPendingLayerChanges |
| void GraphicsLayerClutter::flushCompositingState(const FloatRect& clipRect) |
| { |
| TransformState state(TransformState::UnapplyInverseTransformDirection, FloatQuad(clipRect)); |
| recursiveCommitChanges(state); |
| } |
| |
| void GraphicsLayerClutter::recursiveCommitChanges(const TransformState& state, float pageScaleFactor, const FloatPoint& positionRelativeToBase, bool affectedByPageScale) |
| { |
| // FIXME: Save the state before sending down to kids and restore it after |
| TransformState localState = state; |
| |
| if (appliesPageScale()) { |
| pageScaleFactor = this->pageScaleFactor(); |
| affectedByPageScale = true; |
| } |
| |
| // Accumulate an offset from the ancestral pixel-aligned layer. |
| FloatPoint baseRelativePosition = positionRelativeToBase; |
| if (affectedByPageScale) |
| baseRelativePosition += m_position; |
| |
| commitLayerChangesBeforeSublayers(pageScaleFactor, baseRelativePosition); |
| |
| const Vector<GraphicsLayer*>& childLayers = children(); |
| size_t numChildren = childLayers.size(); |
| |
| for (size_t i = 0; i < numChildren; ++i) { |
| GraphicsLayerClutter* curChild = static_cast<GraphicsLayerClutter*>(childLayers[i]); |
| curChild->recursiveCommitChanges(localState, pageScaleFactor, baseRelativePosition, affectedByPageScale); |
| } |
| |
| commitLayerChangesAfterSublayers(); |
| } |
| |
| void GraphicsLayerClutter::flushCompositingStateForThisLayerOnly() |
| { |
| float pageScaleFactor; |
| FloatPoint offset = computePositionRelativeToBase(pageScaleFactor); |
| commitLayerChangesBeforeSublayers(pageScaleFactor, offset); |
| commitLayerChangesAfterSublayers(); |
| } |
| |
| void GraphicsLayerClutter::commitLayerChangesAfterSublayers() |
| { |
| if (!m_uncommittedChanges) |
| return; |
| |
| m_uncommittedChanges = NoChange; |
| } |
| void GraphicsLayerClutter::noteSublayersChanged() |
| { |
| noteLayerPropertyChanged(ChildrenChanged); |
| } |
| |
| void GraphicsLayerClutter::noteLayerPropertyChanged(LayerChangeFlags flags) |
| { |
| if (!m_uncommittedChanges && m_client) |
| m_client->notifyFlushRequired(this); // call RenderLayerBacking::notifyFlushRequired |
| |
| m_uncommittedChanges |= flags; |
| } |
| |
| void GraphicsLayerClutter::commitLayerChangesBeforeSublayers(float pageScaleFactor, const FloatPoint& positionRelativeToBase) |
| { |
| if (!m_uncommittedChanges) |
| return; |
| |
| if (m_uncommittedChanges & NameChanged) |
| updateLayerNames(); |
| |
| if (m_uncommittedChanges & ChildrenChanged) |
| updateSublayerList(); |
| |
| if (m_uncommittedChanges & GeometryChanged) |
| updateGeometry(pageScaleFactor, positionRelativeToBase); |
| |
| if (m_uncommittedChanges & TransformChanged) |
| updateTransform(); |
| |
| if (m_uncommittedChanges & DrawsContentChanged) |
| updateLayerDrawsContent(pageScaleFactor, positionRelativeToBase); |
| |
| if (m_uncommittedChanges & DirtyRectsChanged) |
| repaintLayerDirtyRects(); |
| |
| if (m_uncommittedChanges & OpacityChanged) |
| updateOpacityOnLayer(); |
| |
| if (m_uncommittedChanges & AnimationChanged) |
| updateAnimations(); |
| } |
| |
| void GraphicsLayerClutter::updateGeometry(float pageScaleFactor, const FloatPoint& positionRelativeToBase) |
| { |
| // FIXME: Need to support page scaling. |
| clutter_actor_set_position(CLUTTER_ACTOR(m_layer.get()), m_position.x(), m_position.y()); |
| clutter_actor_set_size(CLUTTER_ACTOR(m_layer.get()), m_size.width(), m_size.height()); |
| graphicsLayerActorSetAnchorPoint(m_layer.get(), m_anchorPoint.x(), m_anchorPoint.y(), m_anchorPoint.z()); |
| } |
| |
| // Each GraphicsLayer has the corresponding layer in the platform port. |
| // So whenever the list of child layer changes, the list of GraphicsLayerActor should be updated accordingly. |
| void GraphicsLayerClutter::updateSublayerList() |
| { |
| GraphicsLayerActorList newSublayers; |
| const Vector<GraphicsLayer*>& childLayers = children(); |
| |
| if (childLayers.size() > 0) { |
| size_t numChildren = childLayers.size(); |
| for (size_t i = 0; i < numChildren; ++i) { |
| GraphicsLayerClutter* curChild = static_cast<GraphicsLayerClutter*>(childLayers[i]); |
| GraphicsLayerActor* childLayer = curChild->layerForSuperlayer(); |
| g_assert(GRAPHICS_LAYER_IS_ACTOR(childLayer)); |
| newSublayers.append(childLayer); |
| } |
| |
| for (size_t i = 0; i < newSublayers.size(); i++) { |
| ClutterActor* layerActor = CLUTTER_ACTOR(newSublayers[i].get()); |
| ClutterActor* parentActor = clutter_actor_get_parent(layerActor); |
| if (parentActor) |
| clutter_actor_remove_child(parentActor, layerActor); |
| } |
| } |
| |
| graphicsLayerActorSetSublayers(m_layer.get(), newSublayers); |
| } |
| |
| void GraphicsLayerClutter::updateLayerNames() |
| { |
| clutter_actor_set_name(CLUTTER_ACTOR(m_layer.get()), name().utf8().data()); |
| } |
| |
| void GraphicsLayerClutter::updateTransform() |
| { |
| CoglMatrix matrix = m_transform; |
| graphicsLayerActorSetTransform(primaryLayer(), &matrix); |
| } |
| |
| void GraphicsLayerClutter::updateLayerDrawsContent(float pageScaleFactor, const FloatPoint& positionRelativeToBase) |
| { |
| if (m_drawsContent) { |
| graphicsLayerActorSetDrawsContent(m_layer.get(), TRUE); |
| setNeedsDisplay(); |
| } else { |
| graphicsLayerActorSetDrawsContent(m_layer.get(), FALSE); |
| graphicsLayerActorSetSurface(m_layer.get(), 0); |
| } |
| |
| updateDebugIndicators(); |
| } |
| |
| void GraphicsLayerClutter::setupAnimation(PlatformClutterAnimation* propertyAnim, const Animation* anim, bool additive) |
| { |
| double duration = anim->duration(); |
| if (duration <= 0) |
| duration = cAnimationAlmostZeroDuration; |
| |
| float repeatCount = anim->iterationCount(); |
| if (repeatCount == Animation::IterationCountInfinite) |
| repeatCount = numeric_limits<float>::max(); |
| else if (anim->direction() == Animation::AnimationDirectionAlternate || anim->direction() == Animation::AnimationDirectionAlternateReverse) |
| repeatCount /= 2; |
| |
| PlatformClutterAnimation::FillModeType fillMode = PlatformClutterAnimation::NoFillMode; |
| switch (anim->fillMode()) { |
| case AnimationFillModeNone: |
| fillMode = PlatformClutterAnimation::Forwards; // Use "forwards" rather than "removed" because the style system will remove the animation when it is finished. This avoids a flash. |
| break; |
| case AnimationFillModeBackwards: |
| fillMode = PlatformClutterAnimation::Both; // Use "both" rather than "backwards" because the style system will remove the animation when it is finished. This avoids a flash. |
| break; |
| case AnimationFillModeForwards: |
| fillMode = PlatformClutterAnimation::Forwards; |
| break; |
| case AnimationFillModeBoth: |
| fillMode = PlatformClutterAnimation::Both; |
| break; |
| } |
| |
| propertyAnim->setDuration(duration); |
| propertyAnim->setRepeatCount(repeatCount); |
| propertyAnim->setAutoreverses(anim->direction() == Animation::AnimationDirectionAlternate || anim->direction() == Animation::AnimationDirectionAlternateReverse); |
| propertyAnim->setRemovedOnCompletion(false); |
| propertyAnim->setAdditive(additive); |
| propertyAnim->setFillMode(fillMode); |
| } |
| |
| const TimingFunction* GraphicsLayerClutter::timingFunctionForAnimationValue(const AnimationValue* animValue, const Animation* anim) |
| { |
| if (animValue->timingFunction()) |
| return animValue->timingFunction(); |
| if (anim->isTimingFunctionSet()) |
| return anim->timingFunction().get(); |
| |
| return CubicBezierTimingFunction::defaultTimingFunction(); |
| } |
| |
| PassRefPtr<PlatformClutterAnimation> GraphicsLayerClutter::createBasicAnimation(const Animation* anim, const String& keyPath, bool additive) |
| { |
| RefPtr<PlatformClutterAnimation> basicAnim = PlatformClutterAnimation::create(PlatformClutterAnimation::Basic, keyPath); |
| setupAnimation(basicAnim.get(), anim, additive); |
| return basicAnim; |
| } |
| |
| PassRefPtr<PlatformClutterAnimation>GraphicsLayerClutter::createKeyframeAnimation(const Animation* anim, const String& keyPath, bool additive) |
| { |
| RefPtr<PlatformClutterAnimation> keyframeAnim = PlatformClutterAnimation::create(PlatformClutterAnimation::Keyframe, keyPath); |
| setupAnimation(keyframeAnim.get(), anim, additive); |
| return keyframeAnim; |
| } |
| |
| bool GraphicsLayerClutter::setTransformAnimationKeyframes(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* keyframeAnim, int functionIndex, TransformOperation::OperationType transformOpType, bool isMatrixAnimation, const IntSize& boxSize) |
| { |
| Vector<float> keyTimes; |
| Vector<float> floatValues; |
| Vector<FloatPoint3D> floatPoint3DValues; |
| Vector<TransformationMatrix> transformationMatrixValues; |
| Vector<const TimingFunction*> timingFunctions; |
| |
| bool forwards = animation->directionIsForwards(); |
| |
| for (unsigned i = 0; i < valueList.size(); ++i) { |
| unsigned index = forwards ? i : (valueList.size() - i - 1); |
| const TransformAnimationValue* curValue = static_cast<const TransformAnimationValue*>(valueList.at(index)); |
| keyTimes.append(forwards ? curValue->keyTime() : (1 - curValue->keyTime())); |
| |
| if (isMatrixAnimation) { |
| TransformationMatrix transform; |
| curValue->value()->apply(boxSize, transform); |
| |
| // FIXME: In CoreAnimation case, if any matrix is singular, CA won't animate it correctly. |
| // But I'm not sure clutter also does. Check it later, and then decide |
| // whether removing following lines or not. |
| if (!transform.isInvertible()) |
| return false; |
| |
| transformationMatrixValues.append(transform); |
| } else { |
| const TransformOperation* transformOp = curValue->value()->at(functionIndex); |
| if (isTransformTypeNumber(transformOpType)) { |
| float value; |
| getTransformFunctionValue(transformOp, transformOpType, boxSize, value); |
| floatValues.append(value); |
| } else if (isTransformTypeFloatPoint3D(transformOpType)) { |
| FloatPoint3D value; |
| getTransformFunctionValue(transformOp, transformOpType, boxSize, value); |
| floatPoint3DValues.append(value); |
| } else { |
| TransformationMatrix value; |
| getTransformFunctionValue(transformOp, transformOpType, boxSize, value); |
| transformationMatrixValues.append(value); |
| } |
| } |
| |
| if (i < (valueList.size() - 1)) |
| timingFunctions.append(timingFunctionForAnimationValue(forwards ? curValue : valueList.at(index - 1), animation)); |
| } |
| |
| keyframeAnim->setKeyTimes(keyTimes); |
| |
| if (isTransformTypeNumber(transformOpType)) |
| keyframeAnim->setValues(floatValues); |
| else if (isTransformTypeFloatPoint3D(transformOpType)) |
| keyframeAnim->setValues(floatPoint3DValues); |
| else |
| keyframeAnim->setValues(transformationMatrixValues); |
| |
| keyframeAnim->setTimingFunctions(timingFunctions, !forwards); |
| |
| PlatformClutterAnimation::ValueFunctionType valueFunction = getValueFunctionNameForTransformOperation(transformOpType); |
| if (valueFunction != PlatformClutterAnimation::NoValueFunction) |
| keyframeAnim->setValueFunction(valueFunction); |
| |
| return true; |
| } |
| |
| bool GraphicsLayerClutter::setTransformAnimationEndpoints(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* basicAnim, int functionIndex, TransformOperation::OperationType transformOpType, bool isMatrixAnimation, const IntSize& boxSize) |
| { |
| ASSERT(valueList.size() == 2); |
| |
| bool forwards = animation->directionIsForwards(); |
| |
| unsigned fromIndex = !forwards; |
| unsigned toIndex = forwards; |
| |
| const TransformAnimationValue* startValue = static_cast<const TransformAnimationValue*>(valueList.at(fromIndex)); |
| const TransformAnimationValue* endValue = static_cast<const TransformAnimationValue*>(valueList.at(toIndex)); |
| |
| if (isMatrixAnimation) { |
| TransformationMatrix fromTransform, toTransform; |
| startValue->value()->apply(boxSize, fromTransform); |
| endValue->value()->apply(boxSize, toTransform); |
| |
| // FIXME: If any matrix is singular, CA won't animate it correctly. |
| // So fall back to software animation, But it's not sure in clutter case. |
| // We need to investigate it more. |
| if (!fromTransform.isInvertible() || !toTransform.isInvertible()) |
| return false; |
| |
| basicAnim->setFromValue(fromTransform); |
| basicAnim->setToValue(toTransform); |
| } else { |
| if (isTransformTypeNumber(transformOpType)) { |
| float fromValue; |
| getTransformFunctionValue(startValue->value()->at(functionIndex), transformOpType, boxSize, fromValue); |
| basicAnim->setFromValue(fromValue); |
| |
| float toValue; |
| getTransformFunctionValue(endValue->value()->at(functionIndex), transformOpType, boxSize, toValue); |
| basicAnim->setToValue(toValue); |
| } else if (isTransformTypeFloatPoint3D(transformOpType)) { |
| FloatPoint3D fromValue; |
| getTransformFunctionValue(startValue->value()->at(functionIndex), transformOpType, boxSize, fromValue); |
| basicAnim->setFromValue(fromValue); |
| |
| FloatPoint3D toValue; |
| getTransformFunctionValue(endValue->value()->at(functionIndex), transformOpType, boxSize, toValue); |
| basicAnim->setToValue(toValue); |
| } else { |
| TransformationMatrix fromValue; |
| getTransformFunctionValue(startValue->value()->at(functionIndex), transformOpType, boxSize, fromValue); |
| basicAnim->setFromValue(fromValue); |
| |
| TransformationMatrix toValue; |
| getTransformFunctionValue(endValue->value()->at(functionIndex), transformOpType, boxSize, toValue); |
| basicAnim->setToValue(toValue); |
| } |
| } |
| |
| // This codepath is used for 2-keyframe animations, so we still need to look in the start |
| // for a timing function. Even in the reversing animation case, the first keyframe provides the timing function. |
| const TimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), animation); |
| basicAnim->setTimingFunction(timingFunction, !forwards); |
| |
| PlatformClutterAnimation::ValueFunctionType valueFunction = getValueFunctionNameForTransformOperation(transformOpType); |
| if (valueFunction != PlatformClutterAnimation::NoValueFunction) |
| basicAnim->setValueFunction(valueFunction); |
| |
| return true; |
| } |
| |
| bool GraphicsLayerClutter::appendToUncommittedAnimations(const KeyframeValueList& valueList, const TransformOperations* operations, const Animation* animation, const String& animationName, const IntSize& boxSize, int animationIndex, double timeOffset, bool isMatrixAnimation) |
| { |
| TransformOperation::OperationType transformOp = isMatrixAnimation ? TransformOperation::MATRIX_3D : operations->operations().at(animationIndex)->getOperationType(); |
| bool additive = animationIndex > 0; |
| bool isKeyframe = valueList.size() > 2; |
| |
| RefPtr<PlatformClutterAnimation> clutterAnimation; |
| bool validMatrices = true; |
| if (isKeyframe) { |
| clutterAnimation = createKeyframeAnimation(animation, propertyIdToString(valueList.property()), additive); |
| validMatrices = setTransformAnimationKeyframes(valueList, animation, clutterAnimation.get(), animationIndex, transformOp, isMatrixAnimation, boxSize); |
| } else { |
| clutterAnimation = createBasicAnimation(animation, propertyIdToString(valueList.property()), additive); |
| validMatrices = setTransformAnimationEndpoints(valueList, animation, clutterAnimation.get(), animationIndex, transformOp, isMatrixAnimation, boxSize); |
| } |
| |
| if (!validMatrices) |
| return false; |
| |
| m_uncomittedAnimations.append(LayerPropertyAnimation(clutterAnimation, animationName, valueList.property(), animationIndex, timeOffset)); |
| return true; |
| } |
| |
| bool GraphicsLayerClutter::createTransformAnimationsFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& animationName, double timeOffset, const IntSize& boxSize) |
| { |
| ASSERT(valueList.property() == AnimatedPropertyWebkitTransform); |
| |
| bool hasBigRotation; |
| int listIndex = validateTransformOperations(valueList, hasBigRotation); |
| const TransformOperations* operations = (listIndex >= 0) ? static_cast<const TransformAnimationValue*>(valueList.at(listIndex))->value() : 0; |
| |
| // We need to fall back to software animation if we don't have setValueFunction:, and |
| // we would need to animate each incoming transform function separately. This is the |
| // case if we have a rotation >= 180 or we have more than one transform function. |
| if ((hasBigRotation || (operations && operations->size() > 1)) && !PlatformClutterAnimation::supportsValueFunction()) |
| return false; |
| |
| bool validMatrices = true; |
| |
| // If function lists don't match we do a matrix animation, otherwise we do a component hardware animation. |
| // Also, we can't do component animation unless we have valueFunction, so we need to do matrix animation |
| // if that's not true as well. |
| bool isMatrixAnimation = listIndex < 0 || !PlatformClutterAnimation::supportsValueFunction() || (operations->size() >= 2 && !PlatformClutterAnimation::supportsAdditiveValueFunction()); |
| int numAnimations = isMatrixAnimation ? 1 : operations->size(); |
| |
| for (int animationIndex = 0; animationIndex < numAnimations; ++animationIndex) { |
| if (!appendToUncommittedAnimations(valueList, operations, animation, animationName, boxSize, animationIndex, timeOffset, isMatrixAnimation)) { |
| validMatrices = false; |
| break; |
| } |
| } |
| |
| return validMatrices; |
| } |
| |
| bool GraphicsLayerClutter::createAnimationFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& animationName, double timeOffset) |
| { |
| ASSERT(valueList.property() != AnimatedPropertyWebkitTransform); |
| |
| bool isKeyframe = valueList.size() > 2; |
| bool valuesOK; |
| |
| bool additive = false; |
| int animationIndex = 0; |
| |
| RefPtr<PlatformClutterAnimation> clutterAnimation; |
| |
| if (isKeyframe) { |
| clutterAnimation = createKeyframeAnimation(animation, propertyIdToString(valueList.property()), additive); |
| valuesOK = setAnimationKeyframes(valueList, animation, clutterAnimation.get()); |
| } else { |
| clutterAnimation = createBasicAnimation(animation, propertyIdToString(valueList.property()), additive); |
| valuesOK = setAnimationEndpoints(valueList, animation, clutterAnimation.get()); |
| } |
| |
| if (!valuesOK) |
| return false; |
| |
| m_uncomittedAnimations.append(LayerPropertyAnimation(clutterAnimation, animationName, valueList.property(), animationIndex, timeOffset)); |
| |
| return true; |
| } |
| |
| bool GraphicsLayerClutter::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& animationName, double timeOffset) |
| { |
| ASSERT(!animationName.isEmpty()); |
| |
| if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2) |
| return false; |
| |
| // FIXME: ClutterTimeline seems to support steps timing function. So we need to improve here. |
| // See http://developer.gnome.org/clutter/stable/ClutterTimeline.html#ClutterAnimationMode |
| if (animationHasStepsTimingFunction(valueList, anim)) |
| return false; |
| |
| bool createdAnimations = false; |
| if (valueList.property() == AnimatedPropertyWebkitTransform) |
| createdAnimations = createTransformAnimationsFromKeyframes(valueList, anim, animationName, timeOffset, boxSize); |
| else |
| createdAnimations = createAnimationFromKeyframes(valueList, anim, animationName, timeOffset); |
| |
| if (createdAnimations) |
| noteLayerPropertyChanged(AnimationChanged); |
| |
| return createdAnimations; |
| } |
| |
| void GraphicsLayerClutter::removeAnimation(const String& animationName) |
| { |
| if (!animationIsRunning(animationName)) |
| return; |
| |
| m_animationsToProcess.add(animationName, AnimationProcessingAction(Remove)); |
| noteLayerPropertyChanged(AnimationChanged); |
| } |
| |
| bool GraphicsLayerClutter::removeClutterAnimationFromLayer(AnimatedPropertyID property, const String& animationName, int index) |
| { |
| GraphicsLayerActor* layer = animatedLayer(property); |
| |
| String animationID = animationIdentifier(animationName, property, index); |
| |
| PlatformClutterAnimation* existingAnimation = graphicsLayerActorGetAnimationForKey(layer, animationID); |
| if (!existingAnimation) |
| return false; |
| |
| existingAnimation->removeAnimationForKey(layer, animationID); |
| return true; |
| } |
| |
| void GraphicsLayerClutter::pauseClutterAnimationOnLayer(AnimatedPropertyID property, const String& animationName, int index, double timeOffset) |
| { |
| notImplemented(); |
| } |
| |
| void GraphicsLayerClutter::setAnimationOnLayer(PlatformClutterAnimation* clutterAnim, AnimatedPropertyID property, const String& animationName, int index, double timeOffset) |
| { |
| GraphicsLayerActor* layer = animatedLayer(property); |
| |
| if (timeOffset) |
| clutterAnim->setBeginTime(g_get_real_time() - timeOffset); |
| |
| String animationID = animationIdentifier(animationName, property, index); |
| |
| PlatformClutterAnimation* existingAnimation = graphicsLayerActorGetAnimationForKey(layer, animationID); |
| if (existingAnimation) |
| existingAnimation->removeAnimationForKey(layer, animationID); |
| |
| clutterAnim->addAnimationForKey(layer, animationID); |
| } |
| |
| bool GraphicsLayerClutter::setAnimationEndpoints(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* basicAnim) |
| { |
| bool forwards = animation->directionIsForwards(); |
| |
| unsigned fromIndex = !forwards; |
| unsigned toIndex = forwards; |
| |
| switch (valueList.property()) { |
| case AnimatedPropertyOpacity: { |
| basicAnim->setFromValue(static_cast<const FloatAnimationValue*>(valueList.at(fromIndex))->value()); |
| basicAnim->setToValue(static_cast<const FloatAnimationValue*>(valueList.at(toIndex))->value()); |
| break; |
| } |
| default: |
| ASSERT_NOT_REACHED(); // we don't animate color yet |
| break; |
| } |
| |
| // This codepath is used for 2-keyframe animations, so we still need to look in the start |
| // for a timing function. Even in the reversing animation case, the first keyframe provides the timing function. |
| const TimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), animation); |
| if (timingFunction) |
| basicAnim->setTimingFunction(timingFunction, !forwards); |
| |
| return true; |
| } |
| |
| bool GraphicsLayerClutter::setAnimationKeyframes(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* keyframeAnim) |
| { |
| Vector<float> keyTimes; |
| Vector<float> values; |
| Vector<const TimingFunction*> timingFunctions; |
| |
| bool forwards = animation->directionIsForwards(); |
| |
| for (unsigned i = 0; i < valueList.size(); ++i) { |
| unsigned index = forwards ? i : (valueList.size() - i - 1); |
| const AnimationValue* curValue = valueList.at(index); |
| keyTimes.append(forwards ? curValue->keyTime() : (1 - curValue->keyTime())); |
| |
| switch (valueList.property()) { |
| case AnimatedPropertyOpacity: { |
| const FloatAnimationValue* floatValue = static_cast<const FloatAnimationValue*>(curValue); |
| values.append(floatValue->value()); |
| break; |
| } |
| default: |
| ASSERT_NOT_REACHED(); // we don't animate color yet |
| break; |
| } |
| |
| if (i < (valueList.size() - 1)) |
| timingFunctions.append(timingFunctionForAnimationValue(forwards ? curValue : valueList.at(index - 1), animation)); |
| } |
| |
| keyframeAnim->setKeyTimes(keyTimes); |
| keyframeAnim->setValues(values); |
| keyframeAnim->setTimingFunctions(timingFunctions, !forwards); |
| |
| return true; |
| } |
| |
| GraphicsLayerActor* GraphicsLayerClutter::layerForSuperlayer() const |
| { |
| return m_layer.get(); |
| } |
| |
| GraphicsLayerActor* GraphicsLayerClutter::animatedLayer(AnimatedPropertyID property) const |
| { |
| return primaryLayer(); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(ACCELERATED_COMPOSITING) |