GraphicsLayerQt.cpp   [plain text]


/*
    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "config.h"
#include "GraphicsLayerQt.h"

#if !defined(QT_NO_GRAPHICSVIEW)

#include "FloatRect.h"
#include "GraphicsContext.h"
#include "Image.h"
#include "TranslateTransformOperation.h"
#include "UnitBezier.h"
#include <qgraphicseffect.h>
#include <qgraphicsitem.h>
#include <qgraphicsscene.h>
#include <qgraphicsview.h>
#include <qgraphicswidget.h>
#include <qstyleoption.h>
#include <QtCore/qabstractanimation.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qset.h>
#include <QtCore/qtimer.h>
#include <QtGui/qcolor.h>
#include <QtGui/qpainter.h>
#include <QtGui/qpixmap.h>
#include <QtGui/qpixmapcache.h>
#include <wtf/CurrentTime.h>
#include <wtf/RefCounted.h>

#if USE(TILED_BACKING_STORE)
#include "TiledBackingStore.h"
#include "TiledBackingStoreClient.h"

// The minimum width/height for tiling. We use the same value as the Windows implementation.
#define GRAPHICS_LAYER_TILING_THRESHOLD 2000
#endif

#define QT_DEBUG_RECACHE 0
#define QT_DEBUG_CACHEDUMP 0

#define QT_DEBUG_FPS 0

namespace WebCore {

static const int gMinimumPixmapCacheLimit = 2048;

#ifndef QT_NO_GRAPHICSEFFECT
class MaskEffectQt : public QGraphicsEffect {
public:
    MaskEffectQt(QObject* parent, QGraphicsItem* maskLayer)
        : QGraphicsEffect(parent)
        , m_maskLayer(maskLayer)
    {
    }

    void draw(QPainter* painter)
    {
        // This is a modified clone of QGraphicsOpacityEffect.
        // It's more efficient to do it this way because:
        // (a) We don't need the QBrush abstraction - we always end up using QGraphicsItem::paint
        //     from the mask layer.
        // (b) QGraphicsOpacityEffect detaches the pixmap, which is inefficient on OpenGL.
        const QSize maskSize = sourceBoundingRect().toAlignedRect().size();
        if (!maskSize.isValid() || maskSize.isEmpty()) {
            drawSource(painter);
            return;
        }
        QPixmap maskPixmap(maskSize);

        // We need to do this so the pixmap would have hasAlpha().
        maskPixmap.fill(Qt::transparent);
        QPainter maskPainter(&maskPixmap);
        QStyleOptionGraphicsItem option;
        option.exposedRect = option.rect = maskPixmap.rect();
        maskPainter.setRenderHints(painter->renderHints(), true);
        m_maskLayer->paint(&maskPainter, &option, 0);
        maskPainter.end();

        QPoint offset;
        QPixmap srcPixmap = sourcePixmap(Qt::LogicalCoordinates, &offset, QGraphicsEffect::NoPad);

        // We have to use another intermediate pixmap, to make sure the mask applies only to this item
        // and doesn't modify pixels already painted into this paint-device.
        QPixmap pixmap(srcPixmap.size());
        pixmap.fill(Qt::transparent);

        if (pixmap.isNull())
            return;

        QPainter pixmapPainter(&pixmap);

        pixmapPainter.setRenderHints(painter->renderHints());
        pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);

        // We use drawPixmap rather than detaching, because it's more efficient on OpenGL.
        pixmapPainter.drawPixmap(0, 0, srcPixmap);
        pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
        pixmapPainter.drawPixmap(0, 0, maskPixmap);

        pixmapPainter.end();
        painter->drawPixmap(offset, pixmap);
    }

    QGraphicsItem* m_maskLayer;
};
#endif // QT_NO_GRAPHICSEFFECT

class GraphicsLayerQtImpl : public QGraphicsObject
#if USE(TILED_BACKING_STORE)
                          , public virtual TiledBackingStoreClient
#endif
{
    Q_OBJECT

public:
    // This set of flags help us defer which properties of the layer have been
    // modified by the compositor, so we can know what to look for in the next flush.
    enum ChangeMask {
        NoChanges =                 0,

        ParentChange =              (1L << 0),
        ChildrenChange =            (1L << 1),
        MaskLayerChange =           (1L << 2),
        PositionChange =            (1L << 3),

        AnchorPointChange =         (1L << 4),
        SizeChange  =               (1L << 5),
        TransformChange =           (1L << 6),
        ContentChange =             (1L << 7),

        ContentsOrientationChange = (1L << 8),
        OpacityChange =             (1L << 9),
        ContentsRectChange =        (1L << 10),

        Preserves3DChange =         (1L << 11),
        MasksToBoundsChange =       (1L << 12),
        DrawsContentChange =        (1L << 13),
        ContentsOpaqueChange =      (1L << 14),

        BackfaceVisibilityChange =  (1L << 15),
        ChildrenTransformChange =   (1L << 16),
        DisplayChange =             (1L << 17),
        BackgroundColorChange =     (1L << 18),

        DistributesOpacityChange =  (1L << 19)
    };

    // The compositor lets us special-case images and colors, so we try to do so.
    enum StaticContentType { HTMLContentType, PixmapContentType, ColorContentType, MediaContentType, Canvas3DContentType};

    const GraphicsLayerQtImpl* rootLayer() const;

    GraphicsLayerQtImpl(GraphicsLayerQt* newLayer);
    virtual ~GraphicsLayerQtImpl();

    // reimps from QGraphicsItem
    virtual QPainterPath opaqueArea() const;
    virtual QRectF boundingRect() const;
    virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*);

    // We manage transforms ourselves because transform-origin acts differently in webkit and in Qt,
    // and we need it as a fallback in case we encounter an un-invertible matrix.
    void setBaseTransform(const TransformationMatrix&);
    void updateTransform();

    // let the compositor-API tell us which properties were changed
    void notifyChange(ChangeMask);

    // Actual rendering of the web-content into a QPixmap:
    // We prefer to use our own caching because it gives us a higher level of granularity than
    // QGraphicsItem cache modes - Sometimes we need to cache the contents even though the item
    // needs to be updated, e.g. when the background-color is changed.
    // TODO: investigate if QGraphicsItem caching can be improved to support that out of the box.
    QPixmap recache(const QRegion&);

    // Called when the compositor is ready for us to show the changes on screen.
    // This is called indirectly from ChromeClientQt::setNeedsOneShotDrawingSynchronization
    // (meaning the sync would happen together with the next draw) or
    // ChromeClientQt::scheduleCompositingLayerSync (meaning the sync will happen ASAP)
    void flushChanges(bool recursive = true, bool forceTransformUpdate = false);

#if USE(TILED_BACKING_STORE)
    // reimplementations from TiledBackingStoreClient
    virtual void tiledBackingStorePaintBegin();
    virtual void tiledBackingStorePaint(GraphicsContext*, const IntRect&);
    virtual void tiledBackingStorePaintEnd(const Vector<IntRect>& paintedArea);
    virtual IntRect tiledBackingStoreContentsRect();
    virtual IntRect tiledBackingStoreVisibleRect();
    virtual Color tiledBackingStoreBackgroundColor() const;
#endif

    static bool allowAcceleratedCompositingCache() { return QPixmapCache::cacheLimit() > gMinimumPixmapCacheLimit; }

    void drawLayerContent(QPainter*, const QRect&);

public slots:
    // We need to notify the client (ie. the layer compositor) when the animation actually starts.
    void notifyAnimationStarted();

    // We notify WebCore of a layer changed asynchronously; otherwise we end up calling flushChanges too often.
    void notifySyncRequired();

signals:
    // Optimization: Avoid using QTimer::singleShot().
    void notifyAnimationStartedAsync();

public:
    GraphicsLayerQt* m_layer;

    TransformationMatrix m_baseTransform;
    TransformationMatrix m_transformRelativeToRootLayer;
    bool m_transformAnimationRunning;
    bool m_opacityAnimationRunning;
    bool m_blockNotifySyncRequired;
#ifndef QT_NO_GRAPHICSEFFECT
    QWeakPointer<MaskEffectQt> m_maskEffect;
#endif

    struct ContentData {
        QPixmap pixmap;
        QRegion regionToUpdate;
        bool updateAll;

        QColor contentsBackgroundColor;
        QColor backgroundColor;

        QWeakPointer<QGraphicsObject> mediaLayer;
        StaticContentType contentType;

        float opacity;

        ContentData()
            : updateAll(false)
            , contentType(HTMLContentType)
            , opacity(1.f)
        {
        }

    };

    ContentData m_pendingContent;
    ContentData m_currentContent;

    int m_changeMask;

#if USE(TILED_BACKING_STORE)
    TiledBackingStore* m_tiledBackingStore;
#endif

    QSizeF m_size;
    struct {
        QPixmapCache::Key key;
        QSizeF size;
    } m_backingStore;
#ifndef QT_NO_ANIMATION
    QList<QWeakPointer<QAbstractAnimation> > m_animations;
#endif
    QTimer m_suspendTimer;

    struct State {
        GraphicsLayer* maskLayer;
        FloatPoint pos;
        FloatPoint3D anchorPoint;
        FloatSize size;
        TransformationMatrix transform;
        TransformationMatrix childrenTransform;
        Color backgroundColor;
        Color currentColor;
        GraphicsLayer::CompositingCoordinatesOrientation contentsOrientation;
        float opacity;
        QRect contentsRect;

        bool preserves3D: 1;
        bool masksToBounds: 1;
        bool drawsContent: 1;
        bool contentsOpaque: 1;
        bool backfaceVisibility: 1;
        bool distributeOpacity: 1;
        bool align: 2;

        State()
            : maskLayer(0)
            , contentsOrientation(GraphicsLayer::CompositingCoordinatesTopDown)
            , opacity(1.f)
            , preserves3D(false)
            , masksToBounds(false)
            , drawsContent(false)
            , contentsOpaque(false)
            , backfaceVisibility(true)
            , distributeOpacity(false)
        {
        }
    } m_state;

#ifndef QT_NO_ANIMATION
    friend class AnimationQtBase;
#endif
};

inline GraphicsLayerQtImpl* toGraphicsLayerQtImpl(QGraphicsItem* item)
{
    ASSERT(item);
    return qobject_cast<GraphicsLayerQtImpl*>(item->toGraphicsObject());
}

inline GraphicsLayerQtImpl* toGraphicsLayerQtImpl(QGraphicsObject* item)
{
    return qobject_cast<GraphicsLayerQtImpl*>(item);
}

GraphicsLayerQtImpl::GraphicsLayerQtImpl(GraphicsLayerQt* newLayer)
    : QGraphicsObject(0)
    , m_layer(newLayer)
    , m_transformAnimationRunning(false)
    , m_opacityAnimationRunning(false)
    , m_blockNotifySyncRequired(false)
    , m_changeMask(NoChanges)
#if USE(TILED_BACKING_STORE)
    , m_tiledBackingStore(0)
#endif
{
    // We use graphics-view for compositing-only, not for interactivity.
    setAcceptedMouseButtons(Qt::NoButton);

    // We need to have the item enabled, or else wheel events are not passed to the parent class
    // implementation of wheelEvent, where they are ignored and passed to the item below.
    setEnabled(true);

    connect(this, SIGNAL(notifyAnimationStartedAsync()), this, SLOT(notifyAnimationStarted()), Qt::QueuedConnection);
}

GraphicsLayerQtImpl::~GraphicsLayerQtImpl()
{
    // The compositor manages lifecycle of item, so we do not want the graphicsview system to delete
    // our items automatically.
    const QList<QGraphicsItem*> children = childItems();
    QList<QGraphicsItem*>::const_iterator cit;
    for (cit = children.constBegin(); cit != children.constEnd(); ++cit) {
        if (QGraphicsItem* item = *cit) {
            if (scene())
                scene()->removeItem(item);
            item->setParentItem(0);
        }
    }
#if USE(TILED_BACKING_STORE)
    delete m_tiledBackingStore;
#endif
#ifndef QT_NO_ANIMATION
    // We do, however, own the animations.
    QList<QWeakPointer<QAbstractAnimation> >::iterator it;
    for (it = m_animations.begin(); it != m_animations.end(); ++it)
        if (QAbstractAnimation* anim = it->data())
            delete anim;
#endif
}

const GraphicsLayerQtImpl* GraphicsLayerQtImpl::rootLayer() const
{
    if (const GraphicsLayerQtImpl* parent = toGraphicsLayerQtImpl(parentObject()))
        return parent->rootLayer();
    return this;
}


void GraphicsLayerQtImpl::drawLayerContent(QPainter* painter, const QRect& clipRect)
{
    painter->setClipRect(clipRect, Qt::IntersectClip);
    painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
    GraphicsContext gc(painter);
    m_layer->paintGraphicsLayerContents(gc, clipRect);
}

QPixmap GraphicsLayerQtImpl::recache(const QRegion& regionToUpdate)
{
    if (!m_layer->drawsContent() || m_size.isEmpty() || !m_size.isValid())
        return QPixmap();

#if USE(TILED_BACKING_STORE)
    const bool requiresTiling = (m_state.drawsContent && m_currentContent.contentType == HTMLContentType) && (m_size.width() > GRAPHICS_LAYER_TILING_THRESHOLD || m_size.height() > GRAPHICS_LAYER_TILING_THRESHOLD);
    if (requiresTiling && !m_tiledBackingStore) {
        m_tiledBackingStore = new TiledBackingStore(this);
        m_tiledBackingStore->setTileCreationDelay(0);
        setFlag(ItemUsesExtendedStyleOption, true);
    } else if (!requiresTiling && m_tiledBackingStore) {
        delete m_tiledBackingStore;
        m_tiledBackingStore = 0;
        setFlag(ItemUsesExtendedStyleOption, false);
    }

    if (m_tiledBackingStore) {
        m_tiledBackingStore->adjustVisibleRect();
        const QVector<QRect> rects = regionToUpdate.rects();
        for (int i = 0; i < rects.size(); ++i)
           m_tiledBackingStore->invalidate(rects[i]);
        return QPixmap();
    }
#endif

    QPixmap pixmap;
    QRegion region = regionToUpdate;
    if (QPixmapCache::find(m_backingStore.key, &pixmap)) {
        if (region.isEmpty())
            return pixmap;
        QPixmapCache::remove(m_backingStore.key); // Remove the reference to the pixmap in the cache to avoid a detach.
    }

    {
        bool erased = false;

        // If the pixmap is not in the cache or the view has grown since last cached.
        if (pixmap.isNull() || m_size != m_backingStore.size) {
#if QT_DEBUG_RECACHE
            if (pixmap.isNull())
                qDebug() << "CacheMiss" << this << m_size;
#endif
            bool fill = true;
            QRegion newRegion;
            QPixmap oldPixmap = pixmap;

            // If the pixmap is two small to hold the view contents we enlarge, otherwise just use the old (large) pixmap.
            if (pixmap.width() < m_size.width() || pixmap.height() < m_size.height()) {
#if QT_DEBUG_RECACHE
                qDebug() << "CacheGrow" << this << m_size;
#endif
                pixmap = QPixmap(m_size.toSize());
                pixmap.fill(Qt::transparent);
                newRegion = QRegion(0, 0, m_size.width(), m_size.height());
            }

#if 1
            // Blit the contents of oldPixmap back into the cached pixmap as we are just adding new pixels.
            if (!oldPixmap.isNull()) {
                const QRegion cleanRegion = (QRegion(0, 0, m_size.width(), m_size.height())
                                             & QRegion(0, 0, m_backingStore.size.width(), m_backingStore.size.height())) - regionToUpdate;
                if (!cleanRegion.isEmpty()) {
#if QT_DEBUG_RECACHE
                    qDebug() << "CacheBlit" << this << cleanRegion;
#endif
                    const QRect cleanBounds(cleanRegion.boundingRect());
                    QPainter painter(&pixmap);
                    painter.setCompositionMode(QPainter::CompositionMode_Source);
                    painter.drawPixmap(cleanBounds.topLeft(), oldPixmap, cleanBounds);
                    newRegion -= cleanRegion;
                    fill = false; // We cannot just fill the pixmap.
                }
                oldPixmap = QPixmap();
            }
#endif
            region += newRegion;
            if (fill && !region.isEmpty()) { // Clear the entire pixmap with the background.
#if QT_DEBUG_RECACHE
                qDebug() << "CacheErase" << this << m_size << background;
#endif
                erased = true;
                pixmap.fill(Qt::transparent);
            }
        }
        region &= QRegion(0, 0, m_size.width(), m_size.height());

        // If we have something to draw its time to erase it and render the contents.
        if (!region.isEmpty()) {
#if QT_DEBUG_CACHEDUMP
            static int recacheCount = 0;
            ++recacheCount;
            qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size;
            pixmap.save(QString().sprintf("/tmp/%05d_A.png", recacheCount), "PNG");
#endif

            QPainter painter(&pixmap);
            GraphicsContext gc(&painter);

            painter.setClipRegion(region);

            if (!erased) { // Erase the area in cache that we're drawing into.
                painter.setCompositionMode(QPainter::CompositionMode_Clear);
                painter.fillRect(region.boundingRect(), Qt::transparent);

#if QT_DEBUG_CACHEDUMP
                qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size;
                pixmap.save(QString().sprintf("/tmp/%05d_B.png", recacheCount), "PNG");
#endif
            }

            // Render the actual contents into the cache.
            painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
            m_layer->paintGraphicsLayerContents(gc, region.boundingRect());
            painter.end();

#if QT_DEBUG_CACHEDUMP
            qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size;
            pixmap.save(QString().sprintf("/tmp/%05d_C.png", recacheCount), "PNG");
#endif
        }
        m_backingStore.size = m_size; // Store the used size of the pixmap.
    }

    // Finally insert into the cache and allow a reference there.
    m_backingStore.key = QPixmapCache::insert(pixmap);
    return pixmap;
}

void GraphicsLayerQtImpl::updateTransform()
{
    if (!m_transformAnimationRunning)
        m_baseTransform = m_layer->transform();

    TransformationMatrix localTransform;

    GraphicsLayerQtImpl* parent = toGraphicsLayerQtImpl(parentObject());

    // WebCore has relative-to-size originPoint, where as the QGraphicsView has a pixel originPoint.
    // Thus, we need to convert here as we have to manage this outselves due to the fact that the
    // transformOrigin of the graphicsview is imcompatible.
    const qreal originX = m_state.anchorPoint.x() * m_size.width();
    const qreal originY = m_state.anchorPoint.y() * m_size.height();

    // We ignore QGraphicsItem::pos completely, and use transforms only, due to the fact that we
    // have to maintain that ourselves for 3D.
    localTransform
            .translate3d(originX + m_state.pos.x(), originY + m_state.pos.y(), m_state.anchorPoint.z())
            .multiply(m_baseTransform)
            .translate3d(-originX, -originY, -m_state.anchorPoint.z());

    // This is the actual 3D transform of this item, with the ancestors' transform baked in.
    m_transformRelativeToRootLayer = TransformationMatrix(parent ? parent->m_transformRelativeToRootLayer : TransformationMatrix())
                                         .multiply(localTransform);

    // Now we have enough information to determine if the layer is facing backwards.
    if (!m_state.backfaceVisibility && m_transformRelativeToRootLayer.inverse().m33() < 0) {
        setVisible(false);
        // No point in making extra calculations for invisible elements.
        return;
    }

    // The item is front-facing or backface-visibility is on.
    setVisible(true);

    // Flatten to 2D-space of this item if it doesn't preserve 3D.
    if (!m_state.preserves3D) {
        m_transformRelativeToRootLayer.setM13(0);
        m_transformRelativeToRootLayer.setM23(0);
        m_transformRelativeToRootLayer.setM31(0);
        m_transformRelativeToRootLayer.setM32(0);
        m_transformRelativeToRootLayer.setM33(1);
        m_transformRelativeToRootLayer.setM34(0);
        m_transformRelativeToRootLayer.setM43(0);
    }

    // Apply perspective for the use of this item's children. Perspective is always applied from the item's
    // center.
    if (!m_state.childrenTransform.isIdentity()) {
        m_transformRelativeToRootLayer
            .translate(m_size.width() / 2, m_size.height() /2)
            .multiply(m_state.childrenTransform)
            .translate(-m_size.width() / 2, -m_size.height() /2);
    }

    bool inverseOk = true;
    // Use QTransform::inverse to extrapolate the relative transform of this item, based on the parent's
    // transform relative to the root layer and the desired transform for this item relative to the root layer.
    const QTransform parentTransform = parent ? parent->itemTransform(rootLayer()) : QTransform();
    const QTransform transform2D = QTransform(m_transformRelativeToRootLayer) * parentTransform.inverted(&inverseOk);

    // In rare cases the transformation cannot be inversed - in that case we don't apply the transformation at
    // all, otherwise we'd flicker. FIXME: This should be amended when Qt moves to a real 3D scene-graph.
    if (!inverseOk)
        return;

    setTransform(transform2D);

    const QList<QGraphicsItem*> children = childItems();
    QList<QGraphicsItem*>::const_iterator it;
    for (it = children.constBegin(); it != children.constEnd(); ++it)
        if (GraphicsLayerQtImpl* layer= toGraphicsLayerQtImpl(*it))
            layer->updateTransform();
}

void GraphicsLayerQtImpl::setBaseTransform(const TransformationMatrix& baseTransform)
{
    m_baseTransform = baseTransform;
    updateTransform();
}

QPainterPath GraphicsLayerQtImpl::opaqueArea() const
{
    QPainterPath painterPath;

    // We try out best to return the opaque area, maybe it will help graphics-view render less items.
    if (m_currentContent.backgroundColor.isValid() && m_currentContent.backgroundColor.alpha() == 0xff)
        painterPath.addRect(boundingRect());
    else {
        if (m_state.contentsOpaque
            || (m_currentContent.contentType == ColorContentType && m_currentContent.contentsBackgroundColor.alpha() == 0xff)
            || (m_currentContent.contentType == MediaContentType)
            || (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlpha())) {
            painterPath.addRect(m_state.contentsRect);
        }
    }
    return painterPath;
}

QRectF GraphicsLayerQtImpl::boundingRect() const
{
    return QRectF(QPointF(0, 0), QSizeF(m_size));
}

void GraphicsLayerQtImpl::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
#if USE(TILED_BACKING_STORE)
    // FIXME: There's currently no Qt API to know if a new region of an item is exposed outside of the paint event.
    // Suggested for Qt: http://bugreports.qt.nokia.com/browse/QTBUG-14877.
    if (m_tiledBackingStore)
        m_tiledBackingStore->adjustVisibleRect();
#endif

    if (m_currentContent.backgroundColor.isValid())
        painter->fillRect(option->exposedRect, QColor(m_currentContent.backgroundColor));

    switch (m_currentContent.contentType) {
    case HTMLContentType:
        if (m_state.drawsContent) {
            if (!allowAcceleratedCompositingCache())
                drawLayerContent(painter, option->exposedRect.toRect());
            else {
                QPixmap backingStore;
                // We might need to recache, in case we try to paint and the cache was purged (e.g. if it was full).
                if (!QPixmapCache::find(m_backingStore.key, &backingStore) || backingStore.size() != m_size.toSize())
                    backingStore = recache(QRegion(m_state.contentsRect));
                painter->drawPixmap(0, 0, backingStore);
            }
        }
        break;
    case PixmapContentType:
        painter->drawPixmap(m_state.contentsRect, m_currentContent.pixmap);
        break;
    case ColorContentType:
        painter->fillRect(m_state.contentsRect, m_currentContent.contentsBackgroundColor);
        break;
    case MediaContentType:
        // we don't need to paint anything: we have a QGraphicsItem from the media element
        break;
    }
}

void GraphicsLayerQtImpl::notifySyncRequired()
{
    m_blockNotifySyncRequired = false;

    if (m_layer->client())
        m_layer->client()->notifySyncRequired(m_layer);
}

void GraphicsLayerQtImpl::notifyChange(ChangeMask changeMask)
{
    m_changeMask |= changeMask;

    if (m_blockNotifySyncRequired)
        return;

    static QMetaMethod syncMethod = staticMetaObject.method(staticMetaObject.indexOfMethod("notifySyncRequired()"));
    syncMethod.invoke(this, Qt::QueuedConnection);

    m_blockNotifySyncRequired = true;
}

void GraphicsLayerQtImpl::flushChanges(bool recursive, bool forceUpdateTransform)
{
    // This is the bulk of the work. understanding what the compositor is trying to achieve, what
    // graphicsview can do, and trying to find a sane common-ground.
    if (!m_layer || m_changeMask == NoChanges)
        goto afterLayerChanges;

    if (m_changeMask & ParentChange) {
        // The WebCore compositor manages item ownership. We have to make sure graphicsview doesn't
        // try to snatch that ownership.
        if (!m_layer->parent() && !parentItem())
            setParentItem(0);
        else if (m_layer && m_layer->parent() && m_layer->parent()->platformLayer() != parentItem())
            setParentItem(m_layer->parent()->platformLayer());
    }

    if (m_changeMask & ChildrenChange) {
        // We basically do an XOR operation on the list of current children and the list of wanted
        // children, and remove/add.
        QSet<QGraphicsItem*> newChildren;
        const Vector<GraphicsLayer*> newChildrenVector = (m_layer->children());
        newChildren.reserve(newChildrenVector.size());

        for (size_t i = 0; i < newChildrenVector.size(); ++i)
            newChildren.insert(newChildrenVector[i]->platformLayer());

        const QSet<QGraphicsItem*> currentChildren = childItems().toSet();
        const QSet<QGraphicsItem*> childrenToAdd = newChildren - currentChildren;
        const QSet<QGraphicsItem*> childrenToRemove = currentChildren - newChildren;

        QSet<QGraphicsItem*>::const_iterator it;
        for (it = childrenToAdd.constBegin(); it != childrenToAdd.constEnd(); ++it) {
             if (QGraphicsItem* w = *it)
                w->setParentItem(this);
        }

        QSet<QGraphicsItem*>::const_iterator rit;
        for (rit = childrenToRemove.constBegin(); rit != childrenToRemove.constEnd(); ++rit) {
             if (GraphicsLayerQtImpl* w = toGraphicsLayerQtImpl(*rit))
                w->setParentItem(0);
        }

        // Children are ordered by z-value, let graphicsview know.
        for (size_t i = 0; i < newChildrenVector.size(); ++i) {
            if (newChildrenVector[i]->platformLayer())
                newChildrenVector[i]->platformLayer()->setZValue(i);
        }
    }

    if (m_changeMask & MaskLayerChange) {
        // We can't paint here, because we don't know if the mask layer itself is ready... we'll have
        // to wait till this layer tries to paint.
        setFlag(ItemClipsChildrenToShape, m_layer->maskLayer() || m_layer->masksToBounds());
#ifndef QT_NO_GRAPHICSEFFECT
        setGraphicsEffect(0);
        if (m_layer->maskLayer()) {
            if (GraphicsLayerQtImpl* mask = toGraphicsLayerQtImpl(m_layer->maskLayer()->platformLayer())) {
                mask->m_maskEffect = new MaskEffectQt(this, mask);
                setGraphicsEffect(mask->m_maskEffect.data());
            }
        }
#endif
    }

    if (m_changeMask & SizeChange) {
        if (m_layer->size() != m_state.size) {
            prepareGeometryChange();
            m_size = QSizeF(m_layer->size().width(), m_layer->size().height());
        }
    }

    // FIXME: This is a hack, due to a probable QGraphicsScene bug when rapidly modifying the perspective
    // but without this line we get graphic artifacts.
    if ((m_changeMask & ChildrenTransformChange) && m_state.childrenTransform != m_layer->childrenTransform())
        if (scene())
            scene()->update();

    if (m_changeMask & (ChildrenTransformChange | Preserves3DChange | TransformChange | AnchorPointChange | SizeChange | BackfaceVisibilityChange | PositionChange | ParentChange)) {
        // Due to the differences between the way WebCore handles transforms and the way Qt handles transforms,
        // all these elements affect the transforms of all the descendants.
        forceUpdateTransform = true;
    }

    if (m_changeMask & (ContentChange | DrawsContentChange | MaskLayerChange)) {
        switch (m_pendingContent.contentType) {
        case PixmapContentType:
            update();
            setFlag(ItemHasNoContents, false);
            break;

        case MediaContentType:
            setFlag(ItemHasNoContents, true);
            m_pendingContent.mediaLayer.data()->setParentItem(this);
            break;

        case ColorContentType:
            if (m_pendingContent.contentType != m_currentContent.contentType
                || m_pendingContent.contentsBackgroundColor != m_currentContent.contentsBackgroundColor)
                update();
            m_state.drawsContent = false;
            setFlag(ItemHasNoContents, false);

            // Only use ItemUsesExtendedStyleOption for HTML content as colors don't gain much from that.
            setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, false);
            break;

        case HTMLContentType:
            if (m_pendingContent.contentType != m_currentContent.contentType)
                update();
            else if (!m_state.drawsContent && m_layer->drawsContent())
                update();

            setFlag(ItemHasNoContents, !m_layer->drawsContent());
            break;
        }
    }

    if ((m_changeMask & OpacityChange) && m_state.opacity != m_layer->opacity() && !m_opacityAnimationRunning)
        setOpacity(m_layer->opacity());

    if (m_changeMask & ContentsRectChange) {
        const QRect rect(m_layer->contentsRect());
        if (m_state.contentsRect != rect) {
            m_state.contentsRect = rect;
            if (m_pendingContent.mediaLayer) {
                QGraphicsWidget* widget = qobject_cast<QGraphicsWidget*>(m_pendingContent.mediaLayer.data());
                if (widget)
                    widget->setGeometry(rect);
            }
            update();
        }
    }

    if ((m_changeMask & MasksToBoundsChange) && m_state.masksToBounds != m_layer->masksToBounds()) {
        setFlag(QGraphicsItem::ItemClipsToShape, m_layer->masksToBounds());
        setFlag(QGraphicsItem::ItemClipsChildrenToShape, m_layer->masksToBounds());
    }

    if ((m_changeMask & ContentsOpaqueChange) && m_state.contentsOpaque != m_layer->contentsOpaque())
        prepareGeometryChange();

#ifndef QT_NO_GRAPHICSEFFECT
    if (m_maskEffect)
        m_maskEffect.data()->update();
    else
#endif
    if (m_changeMask & DisplayChange) {
#ifndef QT_GRAPHICS_LAYER_NO_RECACHE_ON_DISPLAY_CHANGE
        // Recache now: all the content is ready and we don't want to wait until the paint event.
        // We only need to do this for HTML content, there's no point in caching directly composited
        // content like images or solid rectangles.
        if (m_pendingContent.contentType == HTMLContentType && allowAcceleratedCompositingCache())
            recache(m_pendingContent.regionToUpdate);
#endif
        update(m_pendingContent.regionToUpdate.boundingRect());
        m_pendingContent.regionToUpdate = QRegion();
    }

    if ((m_changeMask & BackgroundColorChange)
        && (m_pendingContent.backgroundColor != m_currentContent.backgroundColor))
        update();

    m_state.maskLayer = m_layer->maskLayer();
    m_state.pos = m_layer->position();
    m_state.anchorPoint = m_layer->anchorPoint();
    m_state.size = m_layer->size();
    m_state.transform = m_layer->transform();
    m_state.contentsOrientation =m_layer->contentsOrientation();
    m_state.opacity = m_layer->opacity();
    m_state.contentsRect = m_layer->contentsRect();
    m_state.preserves3D = m_layer->preserves3D();
    m_state.masksToBounds = m_layer->masksToBounds();
    m_state.drawsContent = m_layer->drawsContent();
    m_state.contentsOpaque = m_layer->contentsOpaque();
    m_state.backfaceVisibility = m_layer->backfaceVisibility();
    m_state.childrenTransform = m_layer->childrenTransform();
    m_currentContent.pixmap = m_pendingContent.pixmap;
    m_currentContent.contentType = m_pendingContent.contentType;
    m_currentContent.mediaLayer = m_pendingContent.mediaLayer;
    m_currentContent.backgroundColor = m_pendingContent.backgroundColor;
    m_currentContent.contentsBackgroundColor = m_pendingContent.contentsBackgroundColor;
    m_pendingContent.regionToUpdate = QRegion();
    m_changeMask = NoChanges;

afterLayerChanges:
    if (forceUpdateTransform)
        updateTransform();

    if (!recursive)
        return;

    QList<QGraphicsItem*> children = childItems();
    if (m_state.maskLayer)
        children.append(m_state.maskLayer->platformLayer());

    QList<QGraphicsItem*>::const_iterator it;
    for (it = children.constBegin(); it != children.constEnd(); ++it) {
        if (QGraphicsItem* item = *it) {
            if (GraphicsLayerQtImpl* layer = toGraphicsLayerQtImpl(item))
                layer->flushChanges(true, forceUpdateTransform);
        }
    }
}

#if USE(TILED_BACKING_STORE)
/* \reimp (TiledBackingStoreClient.h)
*/
void GraphicsLayerQtImpl::tiledBackingStorePaintBegin()
{
}

/* \reimp (TiledBackingStoreClient.h)
*/
void GraphicsLayerQtImpl::tiledBackingStorePaint(GraphicsContext* gc,  const IntRect& rect)
{
    m_layer->paintGraphicsLayerContents(*gc, rect);
}

/* \reimp (TiledBackingStoreClient.h)
*/
void GraphicsLayerQtImpl::tiledBackingStorePaintEnd(const Vector<IntRect>& paintedArea)
{
    for (int i = 0; i < paintedArea.size(); ++i)
        update(QRectF(paintedArea[i]));
}

/* \reimp (TiledBackingStoreClient.h)
*/
IntRect GraphicsLayerQtImpl::tiledBackingStoreContentsRect()
{
    return m_layer->contentsRect();
}

/* \reimp (TiledBackingStoreClient.h)
*/
Color GraphicsLayerQtImpl::tiledBackingStoreBackgroundColor() const
{
    if (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlphaChannel())
        return Color(0, 0, 0);
    // We return a transparent color so that the tiles initialize with alpha.
    return Color(0, 0, 0, 0);
}

IntRect GraphicsLayerQtImpl::tiledBackingStoreVisibleRect()
{
    const QGraphicsView* view = scene()->views().isEmpty() ? 0 : scene()->views().first();
    if (!view)
        return mapFromScene(scene()->sceneRect()).boundingRect().toAlignedRect();

    // All we get is the viewport's visible region. We have to map it to the scene and then to item coordinates.
    return mapFromScene(view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect()).boundingRect().toAlignedRect();
}
#endif

void GraphicsLayerQtImpl::notifyAnimationStarted()
{
    // WebCore notifies javascript when the animation starts. Here we're letting it know.
    m_layer->client()->notifyAnimationStarted(m_layer, /* DOM time */ WTF::currentTime());
}

GraphicsLayerQt::GraphicsLayerQt(GraphicsLayerClient* client)
    : GraphicsLayer(client)
    , m_impl(adoptPtr(new GraphicsLayerQtImpl(this)))
{
}

GraphicsLayerQt::~GraphicsLayerQt()
{
    // Do cleanup while we can still safely call methods on the derived class.
    willBeDestroyed();
}

void GraphicsLayerQt::willBeDestroyed()
{
    m_impl = nullptr;
    GraphicsLayer::willBeDestroyed();
}

// This is the hook for WebCore compositor to know that Qt implements compositing with GraphicsLayerQt.
PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client)
{
    return adoptPtr(new GraphicsLayerQt(client));
}

/* \reimp (GraphicsLayer.h): The current size might change, thus we need to update the whole display.
*/
void GraphicsLayerQt::setNeedsDisplay()
{
    m_impl->m_pendingContent.regionToUpdate = QRegion(QRect(QPoint(0, 0), QSize(size().width(), size().height())));
    m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setNeedsDisplayInRect(const FloatRect& rect)
{
    m_impl->m_pendingContent.regionToUpdate |= QRectF(rect).toAlignedRect();
    m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange);
}

void GraphicsLayerQt::setContentsNeedsDisplay()
{
    switch (m_impl->m_pendingContent.contentType) {
    case GraphicsLayerQtImpl::MediaContentType:
        if (!m_impl->m_pendingContent.mediaLayer)
            return;
        m_impl->m_pendingContent.mediaLayer.data()->update();
        break;
    default:
        setNeedsDisplay();
        break;
    }
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setName(const String& name)
{
    m_impl->setObjectName(name);
    GraphicsLayer::setName(name);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setParent(GraphicsLayer* layer)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange);
    GraphicsLayer::setParent(layer);
}

/* \reimp (GraphicsLayer.h)
*/
bool GraphicsLayerQt::setChildren(const Vector<GraphicsLayer*>& children)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
    return GraphicsLayer::setChildren(children);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::addChild(GraphicsLayer* layer)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
    GraphicsLayer::addChild(layer);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::addChildAtIndex(GraphicsLayer* layer, int index)
{
    GraphicsLayer::addChildAtIndex(layer, index);
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::addChildAbove(GraphicsLayer* layer, GraphicsLayer* sibling)
{
     GraphicsLayer::addChildAbove(layer, sibling);
     m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::addChildBelow(GraphicsLayer* layer, GraphicsLayer* sibling)
{

    GraphicsLayer::addChildBelow(layer, sibling);
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
}

/* \reimp (GraphicsLayer.h)
*/
bool GraphicsLayerQt::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild)
{
    if (GraphicsLayer::replaceChild(oldChild, newChild)) {
        m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
        return true;
    }

    return false;
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::removeFromParent()
{
    if (parent())
        m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange);
    GraphicsLayer::removeFromParent();
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setMaskLayer(GraphicsLayer* value)
{
    if (value == maskLayer())
        return;
    GraphicsLayer::setMaskLayer(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::MaskLayerChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setPosition(const FloatPoint& value)
{
    if (value == position())
        return;
    GraphicsLayer::setPosition(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::PositionChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setAnchorPoint(const FloatPoint3D& value)
{
    if (value == anchorPoint())
        return;
    GraphicsLayer::setAnchorPoint(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::AnchorPointChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setSize(const FloatSize& value)
{
    if (value == size())
        return;
    GraphicsLayer::setSize(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::SizeChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setTransform(const TransformationMatrix& value)
{
    if (value == transform())
        return;
    GraphicsLayer::setTransform(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::TransformChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setChildrenTransform(const TransformationMatrix& value)
{
    if (value == childrenTransform())
        return;
    GraphicsLayer::setChildrenTransform(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenTransformChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setPreserves3D(bool value)
{
    if (value == preserves3D())
        return;
    GraphicsLayer::setPreserves3D(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::Preserves3DChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setMasksToBounds(bool value)
{
    if (value == masksToBounds())
        return;
    GraphicsLayer::setMasksToBounds(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::MasksToBoundsChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setDrawsContent(bool value)
{
    if (value == drawsContent())
        return;
    m_impl->notifyChange(GraphicsLayerQtImpl::DrawsContentChange);
    GraphicsLayer::setDrawsContent(value);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setBackgroundColor(const Color& value)
{
    if (value == m_impl->m_pendingContent.backgroundColor)
        return;
    m_impl->m_pendingContent.backgroundColor = value;
    GraphicsLayer::setBackgroundColor(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::clearBackgroundColor()
{
    if (!m_impl->m_pendingContent.backgroundColor.isValid())
        return;
    m_impl->m_pendingContent.backgroundColor = QColor();
    GraphicsLayer::clearBackgroundColor();
    m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setContentsOpaque(bool value)
{
    if (value == contentsOpaque())
        return;
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOpaqueChange);
    GraphicsLayer::setContentsOpaque(value);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setBackfaceVisibility(bool value)
{
    if (value == backfaceVisibility())
        return;
    GraphicsLayer::setBackfaceVisibility(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::BackfaceVisibilityChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setOpacity(float value)
{
    if (value == opacity())
        return;
    GraphicsLayer::setOpacity(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setContentsRect(const IntRect& value)
{
    if (value == contentsRect())
        return;
    GraphicsLayer::setContentsRect(value);
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsRectChange);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setContentsToImage(Image* image)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange);
    m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType;
    GraphicsLayer::setContentsToImage(image);
    if (image) {
        QPixmap* pxm = image->nativeImageForCurrentFrame();
        if (pxm) {
            m_impl->m_pendingContent.pixmap = *pxm;
            m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::PixmapContentType;
            return;
        }
    }
    m_impl->m_pendingContent.pixmap = QPixmap();
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setContentsToBackgroundColor(const Color& color)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange);
    m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::ColorContentType;
    m_impl->m_pendingContent.contentsBackgroundColor = QColor(color);
    GraphicsLayer::setContentsToBackgroundColor(color);
}

void GraphicsLayerQt::setContentsToMedia(PlatformLayer* media)
{
    if (media) {
        m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::MediaContentType;
        m_impl->m_pendingContent.mediaLayer = media->toGraphicsObject();
    } else
        m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType;

    m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange);
    GraphicsLayer::setContentsToMedia(media);
}

void GraphicsLayerQt::setContentsToCanvas(PlatformLayer* canvas)
{
    setContentsToMedia(canvas);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::setContentsOrientation(CompositingCoordinatesOrientation orientation)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOrientationChange);
    GraphicsLayer::setContentsOrientation(orientation);
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::distributeOpacity(float o)
{
    m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange);
    m_impl->m_state.distributeOpacity = true;
}

/* \reimp (GraphicsLayer.h)
*/
float GraphicsLayerQt::accumulatedOpacity() const
{
    return m_impl->effectiveOpacity();
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::syncCompositingState(const FloatRect&)
{
    m_impl->flushChanges();
    GraphicsLayer::syncCompositingState();
}

/* \reimp (GraphicsLayer.h)
*/
void GraphicsLayerQt::syncCompositingStateForThisLayerOnly()
{
    // We can't call flushChanges recursively here
    m_impl->flushChanges(false);
    GraphicsLayer::syncCompositingStateForThisLayerOnly();
}

/* \reimp (GraphicsLayer.h)
*/
PlatformLayer* GraphicsLayerQt::platformLayer() const
{
    return m_impl.get();
}

// Now we start dealing with WebCore animations translated to Qt animations

template <typename T>
struct KeyframeValueQt {
    const TimingFunction* timingFunction;
    T value;
};

/* Copied from AnimationBase.cpp
*/
static inline double solveEpsilon(double duration)
{
    return 1.0 / (200.0 * duration);
}

static inline double solveCubicBezierFunction(qreal p1x, qreal p1y, qreal p2x, qreal p2y, double t, double duration)
{
    UnitBezier bezier(p1x, p1y, p2x, p2y);
    return bezier.solve(t, solveEpsilon(duration));
}

static inline double solveStepsFunction(int numSteps, bool stepAtStart, double t)
{
    if (stepAtStart)
        return qMin(1.0, (floor(numSteps * t) + 1) / numSteps);
    return floor(numSteps * t) / numSteps;
}

static inline qreal applyTimingFunction(const TimingFunction* timingFunction, qreal progress, double duration)
{
    // We want the timing function to be as close as possible to what the web-developer intended, so
    // we're using the same function used by WebCore when compositing is disabled. Using easing-curves
    // would probably work for some of the cases, but wouldn't really buy us anything as we'd have to
    // convert the bezier function back to an easing curve.

    if (timingFunction->isCubicBezierTimingFunction()) {
        const CubicBezierTimingFunction* ctf = static_cast<const CubicBezierTimingFunction*>(timingFunction);
        return solveCubicBezierFunction(ctf->x1(),
                                        ctf->y1(),
                                        ctf->x2(),
                                        ctf->y2(),
                                        double(progress), double(duration) / 1000);
    } else if (timingFunction->isStepsTimingFunction()) {
        const StepsTimingFunction* stf = static_cast<const StepsTimingFunction*>(timingFunction);
        return solveStepsFunction(stf->numberOfSteps(), stf->stepAtStart(), double(progress));
    } else
        return progress;
}

// Helper functions to safely get a value out of WebCore's AnimationValue*.

#ifndef QT_NO_ANIMATION
static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, TransformOperations& transformOperations)
{
    transformOperations = TransformOperations();
    if (!animationValue)
        return;

    if (const TransformOperations* ops = static_cast<const TransformAnimationValue*>(animationValue)->value())
        transformOperations = *ops;
}

static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, qreal& realValue)
{
    realValue = animationValue ? static_cast<const FloatAnimationValue*>(animationValue)->value() : 0;
}

// We put a bit of the functionality in a base class to allow casting and to save some code size.

class AnimationQtBase : public QAbstractAnimation {
public:
    AnimationQtBase(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
        : QAbstractAnimation(0)
        , m_layer(layer)
        , m_boxSize(boxSize)
        , m_duration(anim->duration() * 1000)
        , m_isAlternate(anim->direction() == Animation::AnimationDirectionAlternate)
        , m_webkitPropertyID(values.property())
        , m_webkitAnimation(anim)
        , m_keyframesName(name)
        , m_fillsForwards(false)
    {
    }


    virtual AnimatedPropertyID animatedProperty() const = 0;

    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
    {
        QAbstractAnimation::updateState(newState, oldState);

        // For some reason we have do this asynchronously - or the animation won't work.
        if (newState == Running && oldState == Stopped && m_layer.data())
            m_layer.data()->notifyAnimationStartedAsync();
    }

    virtual int duration() const { return m_duration; }

    QWeakPointer<GraphicsLayerQtImpl> m_layer;
    IntSize m_boxSize;
    int m_duration;
    bool m_isAlternate;
    AnimatedPropertyID m_webkitPropertyID;

    // We might need this in case the same animation is added again (i.e. resumed by WebCore).
    const Animation* m_webkitAnimation;
    QString m_keyframesName;
    bool m_fillsForwards;
};

// We'd rather have a templatized QAbstractAnimation than QPropertyAnimation / QVariantAnimation;
// Since we know the types that we're dealing with, the QObject/QProperty/QVariant abstraction
// buys us very little in this case, for too much overhead.
template <typename T>
class AnimationQt : public AnimationQtBase {

public:
    AnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
        : AnimationQtBase(layer, values, boxSize, anim, name)
    {
        // Copying those WebCore structures is not trivial, we have to do it like this.
        for (size_t i = 0; i < values.size(); ++i) {
            const AnimationValue* animationValue = values.at(i);
            KeyframeValueQt<T> keyframeValue;
            if (animationValue->timingFunction())
                keyframeValue.timingFunction = animationValue->timingFunction();
            else
                keyframeValue.timingFunction = anim->timingFunction().get();
            webkitAnimationToQtAnimationValue(animationValue, keyframeValue.value);
            m_keyframeValues[animationValue->keyTime()] = keyframeValue;
        }
    }

protected:

    // This is the part that differs between animated properties.
    virtual void applyFrame(const T& fromValue, const T& toValue, qreal progress) = 0;

    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
    {
#if QT_DEBUG_FPS
        if (newState == Running && oldState == Stopped) {
            qDebug("Animation Started!");
            m_fps.frames = 0;
            m_fps.duration.start();
        } else if (newState == Stopped && oldState == Running) {
            const int duration = m_fps.duration.elapsed();
            qDebug("Animation Ended! %dms [%f FPS]", duration,
                    (1000 / (((float)duration) / m_fps.frames)));
        }
#endif
        AnimationQtBase::updateState(newState, oldState);
    }

    virtual void updateCurrentTime(int currentTime)
    {
        if (!m_layer)
            return;

        qreal progress = qreal(currentLoopTime()) / duration();

        if (m_isAlternate && currentLoop()%2)
            progress = 1-progress;

        if (m_keyframeValues.isEmpty())
            return;

        // Find the current from-to keyframes in our little map.
        typename QMap<qreal, KeyframeValueQt<T> >::iterator it = m_keyframeValues.find(progress);

        // We didn't find an exact match, we try the closest match (lower bound).
        if (it == m_keyframeValues.end())
            it = m_keyframeValues.lowerBound(progress)-1;

        // We didn't find any match; use the first keyframe.
        if (it == m_keyframeValues.end())
            it = m_keyframeValues.begin();

        typename QMap<qreal, KeyframeValueQt<T> >::iterator it2 = it + 1;
        if (it2 == m_keyframeValues.end())
            it2 = it;
        const KeyframeValueQt<T>& fromKeyframe = it.value();
        const KeyframeValueQt<T>& toKeyframe = it2.value();

        const TimingFunction* timingFunc = fromKeyframe.timingFunction;
        const T& fromValue = fromKeyframe.value;
        const T& toValue = toKeyframe.value;

        // Now we have a source keyframe, origin keyframe and a timing function.
        // We can now process the progress and apply the frame.
        progress = (!progress || progress == 1 || it.key() == it2.key()) ?
            progress : applyTimingFunction(timingFunc, (progress - it.key()) / (it2.key() - it.key()), duration());
        applyFrame(fromValue, toValue, progress);
#if QT_DEBUG_FPS
        ++m_fps.frames;
#endif
    }

    QMap<qreal, KeyframeValueQt<T> > m_keyframeValues;
#if QT_DEBUG_FPS
    struct {
        QTime duration;
        int frames;
    } m_fps;
#endif
};

class TransformAnimationQt : public AnimationQt<TransformOperations> {
public:
    TransformAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
        : AnimationQt<TransformOperations>(layer, values, boxSize, anim, name)
    {
    }

    ~TransformAnimationQt()
    {
        if (m_fillsForwards)
            setCurrentTime(1);
    }

    virtual AnimatedPropertyID animatedProperty() const { return AnimatedPropertyWebkitTransform; }

    // The idea is that we let WebCore manage the transform operations and Qt just manage the
    // animation heartbeat and the bottom-line QTransform. We gain performance, not by using
    // Transform instead of TransformationMatrix, but by proper caching of items that are
    // expensive for WebCore to render. We want the rest to be as close to WebCore's idea as possible.
    virtual void applyFrame(const TransformOperations& sourceOperations, const TransformOperations& targetOperations, qreal progress)
    {
        TransformationMatrix transformMatrix;

        bool validTransformLists = true;
        const int sourceOperationCount = sourceOperations.size();
        if (sourceOperationCount) {
            if (targetOperations.size() != sourceOperationCount)
                validTransformLists = false;
            else {
                for (size_t j = 0; j < sourceOperationCount && validTransformLists; ++j) {
                    if (!sourceOperations.operations()[j]->isSameType(*targetOperations.operations()[j]))
                        validTransformLists = false;
                }
            }
        }

        if (validTransformLists) {
            for (size_t i = 0; i < targetOperations.size(); ++i)
                targetOperations.operations()[i]->blend(sourceOperations.at(i), progress)->apply(transformMatrix, m_boxSize);
        } else {
            targetOperations.apply(m_boxSize, transformMatrix);
            transformMatrix.blend(m_sourceMatrix, progress);
        }

        m_layer.data()->m_layer->setTransform(transformMatrix);
        // We force the actual opacity change, otherwise it would be ignored because of the animation.
        m_layer.data()->setBaseTransform(transformMatrix);
    }

    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
    {
        AnimationQt<TransformOperations>::updateState(newState, oldState);
        if (!m_layer)
            return;

        m_layer.data()->flushChanges(true);

        // To increase FPS, we use a less accurate caching mechanism while animation is going on
        // this is a UX choice that should probably be customizable.
        if (newState == QAbstractAnimation::Running) {
            m_sourceMatrix = m_layer.data()->m_layer->transform();
            m_layer.data()->m_transformAnimationRunning = true;
        } else if (newState == QAbstractAnimation::Stopped) {
            // We update the transform back to the default. This already takes fill-modes into account.
            m_layer.data()->m_transformAnimationRunning = false;
            if (m_layer && m_layer.data()->m_layer)
                m_layer.data()->setBaseTransform(m_layer.data()->m_layer->transform());
        }
    }

    TransformationMatrix m_sourceMatrix;
};

class OpacityAnimationQt : public AnimationQt<qreal> {
public:
    OpacityAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString& name)
         : AnimationQt<qreal>(layer, values, boxSize, anim, name)
    {
    }

    ~OpacityAnimationQt()
    {
        if (m_fillsForwards)
            setCurrentTime(1);
    }

    virtual AnimatedPropertyID animatedProperty() const { return AnimatedPropertyOpacity; }

    virtual void applyFrame(const qreal& fromValue, const qreal& toValue, qreal progress)
    {
        qreal opacity = qBound(qreal(0), fromValue + (toValue - fromValue) * progress, qreal(1));

        // FIXME: This is a hack, due to a probable QGraphicsScene bug.
        // Without this the opacity change doesn't always have immediate effect.
        if (m_layer.data()->scene() && !m_layer.data()->opacity() && opacity)
            m_layer.data()->scene()->update();

        m_layer.data()->m_layer->setOpacity(opacity);
        // We force the actual opacity change, otherwise it would be ignored because of the animation.
        m_layer.data()->setOpacity(opacity);
    }

    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
    {
        AnimationQt<qreal>::updateState(newState, oldState);

        if (m_layer)
            m_layer.data()->m_opacityAnimationRunning = (newState == QAbstractAnimation::Running);

        // If stopped, we update the opacity back to the default. This already takes fill-modes into account.
        if (newState == Stopped)
            if (m_layer && m_layer.data()->m_layer)
                m_layer.data()->setOpacity(m_layer.data()->m_layer->opacity());

    }
};

bool GraphicsLayerQt::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset)
{
    if (!anim->duration() || !anim->iterationCount())
        return false;

    AnimationQtBase* newAnim = 0;

    // Fixed: we might already have the Qt animation object associated with this WebCore::Animation object.
    QList<QWeakPointer<QAbstractAnimation> >::iterator it;
    for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
        if (*it) {
            AnimationQtBase* curAnimation = static_cast<AnimationQtBase*>(it->data());
            if (curAnimation && curAnimation->m_webkitAnimation == anim
                && values.property() == curAnimation->animatedProperty()) {
                newAnim = curAnimation;
                break;
            }
        }
    }

    if (!newAnim) {
        switch (values.property()) {
        case AnimatedPropertyOpacity:
            newAnim = new OpacityAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName);
            break;
        case AnimatedPropertyWebkitTransform:
            newAnim = new TransformAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName);
            break;
        default:
            return false;
        }

        // We make sure WebCore::Animation and QAnimation are on the same terms.
        newAnim->setLoopCount(anim->iterationCount());
        newAnim->m_fillsForwards = anim->fillsForwards();
        m_impl->m_animations.append(QWeakPointer<QAbstractAnimation>(newAnim));
        QObject::connect(&m_impl->m_suspendTimer, SIGNAL(timeout()), newAnim, SLOT(resume()));
    }

    // Flush now to avoid flicker.
    m_impl->flushChanges(false);

    // Qhen fill-mode is backwards/both, we set the value to 0 before the delay takes place.
    if (anim->fillsBackwards())
        newAnim->setCurrentTime(0);

    newAnim->start();

    // We synchronize the animation's clock to WebCore's timeOffset.
    newAnim->setCurrentTime(timeOffset * 1000);

    // We don't need to manage the animation object's lifecycle:
    // WebCore would call removeAnimations when it's time to delete.

    return true;
}

void GraphicsLayerQt::removeAnimationsForProperty(AnimatedPropertyID id)
{
    QList<QWeakPointer<QAbstractAnimation> >::iterator it;
    for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
        if (!(*it))
            continue;

        AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data());
        if (anim && anim->m_webkitPropertyID == id) {
            // We need to stop the animation right away, or it might flicker before it's deleted.
            anim->stop();
            anim->deleteLater();
            it = m_impl->m_animations.erase(it);
            --it;
        }
    }
}

void GraphicsLayerQt::removeAnimationsForKeyframes(const String& name)
{
    QList<QWeakPointer<QAbstractAnimation> >::iterator it;
    for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
        if (!(*it))
            continue;

        AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data());
        if (anim && anim->m_keyframesName == QString(name)) {
            // We need to stop the animation right away, or it might flicker before it's deleted.
            anim->stop();
            anim->deleteLater();
            it = m_impl->m_animations.erase(it);
            --it;
        }
    }
}

void GraphicsLayerQt::pauseAnimation(const String& name, double timeOffset)
{
    QList<QWeakPointer<QAbstractAnimation> >::iterator it;
    for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
        if (!(*it))
            continue;

        AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data());
        if (anim && anim->m_keyframesName == QString(name)) {
            // we synchronize the animation's clock to WebCore's timeOffset
            anim->setCurrentTime(timeOffset * 1000);
            anim->pause();
        }
    }
}

void GraphicsLayerQt::suspendAnimations(double time)
{
    if (m_impl->m_suspendTimer.isActive()) {
        m_impl->m_suspendTimer.stop();
        m_impl->m_suspendTimer.start(time * 1000);
    } else {
        QList<QWeakPointer<QAbstractAnimation> >::iterator it;
        for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
            if (QAbstractAnimation* anim = it->data())
                anim->pause();
        }
    }
}

void GraphicsLayerQt::resumeAnimations()
{
    if (m_impl->m_suspendTimer.isActive()) {
        m_impl->m_suspendTimer.stop();
        QList<QWeakPointer<QAbstractAnimation> >::iterator it;
        for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
            if (QAbstractAnimation* anim = it->data())
                anim->resume();
        }
    }
}

#endif // QT_NO_ANIMATION
}

#include <GraphicsLayerQt.moc>


#endif // QT_NO_GRAPHICSVIEW