1/* 2 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) 3 4 This library is free software; you can redistribute it and/or 5 modify it under the terms of the GNU Library General Public 6 License as published by the Free Software Foundation; either 7 version 2 of the License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Library General Public License for more details. 13 14 You should have received a copy of the GNU Library General Public License 15 along with this library; see the file COPYING.LIB. If not, write to 16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 Boston, MA 02110-1301, USA. 18*/ 19 20#include "config.h" 21#include "GraphicsLayerQt.h" 22 23#if !defined(QT_NO_GRAPHICSVIEW) 24 25#include "CurrentTime.h" 26#include "FloatRect.h" 27#include "GraphicsContext.h" 28#include "Image.h" 29#include "RefCounted.h" 30#include "TranslateTransformOperation.h" 31#include "UnitBezier.h" 32#include <QtCore/qabstractanimation.h> 33#include <QtCore/qdatetime.h> 34#include <QtCore/qdebug.h> 35#include <QtCore/qmetaobject.h> 36#include <QtCore/qset.h> 37#include <QtCore/qtimer.h> 38#include <QtGui/qcolor.h> 39#include <QtGui/qgraphicseffect.h> 40#include <QtGui/qgraphicsitem.h> 41#include <QtGui/qgraphicsscene.h> 42#include <QtGui/qgraphicsview.h> 43#include <QtGui/qgraphicswidget.h> 44#include <QtGui/qpainter.h> 45#include <QtGui/qpixmap.h> 46#include <QtGui/qpixmapcache.h> 47#include <QtGui/qstyleoption.h> 48 49#if ENABLE(TILED_BACKING_STORE) 50#include "TiledBackingStore.h" 51#include "TiledBackingStoreClient.h" 52 53// The minimum width/height for tiling. We use the same value as the Windows implementation. 54#define GRAPHICS_LAYER_TILING_THRESHOLD 2000 55#endif 56 57#define QT_DEBUG_RECACHE 0 58#define QT_DEBUG_CACHEDUMP 0 59 60#define QT_DEBUG_FPS 0 61 62namespace WebCore { 63 64static const int gMinimumPixmapCacheLimit = 2048; 65 66#ifndef QT_NO_GRAPHICSEFFECT 67class MaskEffectQt : public QGraphicsEffect { 68public: 69 MaskEffectQt(QObject* parent, QGraphicsItem* maskLayer) 70 : QGraphicsEffect(parent) 71 , m_maskLayer(maskLayer) 72 { 73 } 74 75 void draw(QPainter* painter) 76 { 77 // This is a modified clone of QGraphicsOpacityEffect. 78 // It's more efficient to do it this way because: 79 // (a) We don't need the QBrush abstraction - we always end up using QGraphicsItem::paint 80 // from the mask layer. 81 // (b) QGraphicsOpacityEffect detaches the pixmap, which is inefficient on OpenGL. 82 const QSize maskSize = sourceBoundingRect().toAlignedRect().size(); 83 if (!maskSize.isValid() || maskSize.isEmpty()) { 84 drawSource(painter); 85 return; 86 } 87 QPixmap maskPixmap(maskSize); 88 89 // We need to do this so the pixmap would have hasAlpha(). 90 maskPixmap.fill(Qt::transparent); 91 QPainter maskPainter(&maskPixmap); 92 QStyleOptionGraphicsItem option; 93 option.exposedRect = option.rect = maskPixmap.rect(); 94 maskPainter.setRenderHints(painter->renderHints(), true); 95 m_maskLayer->paint(&maskPainter, &option, 0); 96 maskPainter.end(); 97 98 QPoint offset; 99 QPixmap srcPixmap = sourcePixmap(Qt::LogicalCoordinates, &offset, QGraphicsEffect::NoPad); 100 101 // We have to use another intermediate pixmap, to make sure the mask applies only to this item 102 // and doesn't modify pixels already painted into this paint-device. 103 QPixmap pixmap(srcPixmap.size()); 104 pixmap.fill(Qt::transparent); 105 106 if (pixmap.isNull()) 107 return; 108 109 QPainter pixmapPainter(&pixmap); 110 111 pixmapPainter.setRenderHints(painter->renderHints()); 112 pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source); 113 114 // We use drawPixmap rather than detaching, because it's more efficient on OpenGL. 115 pixmapPainter.drawPixmap(0, 0, srcPixmap); 116 pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); 117 pixmapPainter.drawPixmap(0, 0, maskPixmap); 118 119 pixmapPainter.end(); 120 painter->drawPixmap(offset, pixmap); 121 } 122 123 QGraphicsItem* m_maskLayer; 124}; 125#endif // QT_NO_GRAPHICSEFFECT 126 127class GraphicsLayerQtImpl : public QGraphicsObject 128#if ENABLE(TILED_BACKING_STORE) 129, public virtual TiledBackingStoreClient 130#endif 131{ 132 Q_OBJECT 133 134public: 135 // This set of flags help us defer which properties of the layer have been 136 // modified by the compositor, so we can know what to look for in the next flush. 137 enum ChangeMask { 138 NoChanges = 0, 139 140 ParentChange = (1L << 0), 141 ChildrenChange = (1L << 1), 142 MaskLayerChange = (1L << 2), 143 PositionChange = (1L << 3), 144 145 AnchorPointChange = (1L << 4), 146 SizeChange = (1L << 5), 147 TransformChange = (1L << 6), 148 ContentChange = (1L << 7), 149 150 ContentsOrientationChange = (1L << 8), 151 OpacityChange = (1L << 9), 152 ContentsRectChange = (1L << 10), 153 154 Preserves3DChange = (1L << 11), 155 MasksToBoundsChange = (1L << 12), 156 DrawsContentChange = (1L << 13), 157 ContentsOpaqueChange = (1L << 14), 158 159 BackfaceVisibilityChange = (1L << 15), 160 ChildrenTransformChange = (1L << 16), 161 DisplayChange = (1L << 17), 162 BackgroundColorChange = (1L << 18), 163 164 DistributesOpacityChange = (1L << 19) 165 }; 166 167 // The compositor lets us special-case images and colors, so we try to do so. 168 enum StaticContentType { HTMLContentType, PixmapContentType, ColorContentType, MediaContentType, Canvas3DContentType}; 169 170 const GraphicsLayerQtImpl* rootLayer() const; 171 172 GraphicsLayerQtImpl(GraphicsLayerQt* newLayer); 173 virtual ~GraphicsLayerQtImpl(); 174 175 // reimps from QGraphicsItem 176 virtual QPainterPath opaqueArea() const; 177 virtual QRectF boundingRect() const; 178 virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); 179 180 // We manage transforms ourselves because transform-origin acts differently in webkit and in Qt, 181 // and we need it as a fallback in case we encounter an un-invertible matrix. 182 void setBaseTransform(const TransformationMatrix&); 183 void updateTransform(); 184 185 // let the compositor-API tell us which properties were changed 186 void notifyChange(ChangeMask); 187 188 // Actual rendering of the web-content into a QPixmap: 189 // We prefer to use our own caching because it gives us a higher level of granularity than 190 // QGraphicsItem cache modes - Sometimes we need to cache the contents even though the item 191 // needs to be updated, e.g. when the background-color is changed. 192 // TODO: investigate if QGraphicsItem caching can be improved to support that out of the box. 193 QPixmap recache(const QRegion&); 194 195 // Called when the compositor is ready for us to show the changes on screen. 196 // This is called indirectly from ChromeClientQt::setNeedsOneShotDrawingSynchronization 197 // (meaning the sync would happen together with the next draw) or 198 // ChromeClientQt::scheduleCompositingLayerSync (meaning the sync will happen ASAP) 199 void flushChanges(bool recursive = true, bool forceTransformUpdate = false); 200 201#if ENABLE(TILED_BACKING_STORE) 202 // reimplementations from TiledBackingStoreClient 203 virtual void tiledBackingStorePaintBegin(); 204 virtual void tiledBackingStorePaint(GraphicsContext*, const IntRect&); 205 virtual void tiledBackingStorePaintEnd(const Vector<IntRect>& paintedArea); 206 virtual IntRect tiledBackingStoreContentsRect(); 207 virtual IntRect tiledBackingStoreVisibleRect(); 208 virtual Color tiledBackingStoreBackgroundColor() const; 209#endif 210 211 static bool allowAcceleratedCompositingCache() { return QPixmapCache::cacheLimit() > gMinimumPixmapCacheLimit; } 212 213 void drawLayerContent(QPainter*, const QRect&); 214 215public slots: 216 // We need to notify the client (ie. the layer compositor) when the animation actually starts. 217 void notifyAnimationStarted(); 218 219 // We notify WebCore of a layer changed asynchronously; otherwise we end up calling flushChanges too often. 220 void notifySyncRequired(); 221 222signals: 223 // Optimization: Avoid using QTimer::singleShot(). 224 void notifyAnimationStartedAsync(); 225 226public: 227 GraphicsLayerQt* m_layer; 228 229 TransformationMatrix m_baseTransform; 230 TransformationMatrix m_transformRelativeToRootLayer; 231 bool m_transformAnimationRunning; 232 bool m_opacityAnimationRunning; 233 bool m_blockNotifySyncRequired; 234#ifndef QT_NO_GRAPHICSEFFECT 235 QWeakPointer<MaskEffectQt> m_maskEffect; 236#endif 237 238 struct ContentData { 239 QPixmap pixmap; 240 QRegion regionToUpdate; 241 bool updateAll; 242 243 QColor contentsBackgroundColor; 244 QColor backgroundColor; 245 246 QWeakPointer<QGraphicsObject> mediaLayer; 247 StaticContentType contentType; 248 249 float opacity; 250 251 ContentData() 252 : updateAll(false) 253 , contentType(HTMLContentType) 254 , opacity(1.f) 255 { 256 } 257 258 }; 259 260 ContentData m_pendingContent; 261 ContentData m_currentContent; 262 263 int m_changeMask; 264 265#if ENABLE(TILED_BACKING_STORE) 266 TiledBackingStore* m_tiledBackingStore; 267#endif 268 269 QSizeF m_size; 270 struct { 271 QPixmapCache::Key key; 272 QSizeF size; 273 } m_backingStore; 274#ifndef QT_NO_ANIMATION 275 QList<QWeakPointer<QAbstractAnimation> > m_animations; 276#endif 277 QTimer m_suspendTimer; 278 279 struct State { 280 GraphicsLayer* maskLayer; 281 FloatPoint pos; 282 FloatPoint3D anchorPoint; 283 FloatSize size; 284 TransformationMatrix transform; 285 TransformationMatrix childrenTransform; 286 Color backgroundColor; 287 Color currentColor; 288 GraphicsLayer::CompositingCoordinatesOrientation contentsOrientation; 289 float opacity; 290 QRect contentsRect; 291 292 bool preserves3D: 1; 293 bool masksToBounds: 1; 294 bool drawsContent: 1; 295 bool contentsOpaque: 1; 296 bool backfaceVisibility: 1; 297 bool distributeOpacity: 1; 298 bool align: 2; 299 300 State() 301 : maskLayer(0) 302 , opacity(1.f) 303 , preserves3D(false) 304 , masksToBounds(false) 305 , drawsContent(false) 306 , contentsOpaque(false) 307 , backfaceVisibility(false) 308 , distributeOpacity(false) 309 { 310 } 311 } m_state; 312 313#ifndef QT_NO_ANIMATION 314 friend class AnimationQtBase; 315#endif 316}; 317 318inline GraphicsLayerQtImpl* toGraphicsLayerQtImpl(QGraphicsItem* item) 319{ 320 ASSERT(item); 321 return qobject_cast<GraphicsLayerQtImpl*>(item->toGraphicsObject()); 322} 323 324inline GraphicsLayerQtImpl* toGraphicsLayerQtImpl(QGraphicsObject* item) 325{ 326 return qobject_cast<GraphicsLayerQtImpl*>(item); 327} 328 329GraphicsLayerQtImpl::GraphicsLayerQtImpl(GraphicsLayerQt* newLayer) 330 : QGraphicsObject(0) 331 , m_layer(newLayer) 332 , m_transformAnimationRunning(false) 333 , m_opacityAnimationRunning(false) 334 , m_blockNotifySyncRequired(false) 335 , m_changeMask(NoChanges) 336#if ENABLE(TILED_BACKING_STORE) 337 , m_tiledBackingStore(0) 338#endif 339{ 340 // We use graphics-view for compositing-only, not for interactivity. 341 setAcceptedMouseButtons(Qt::NoButton); 342 343 // We need to have the item enabled, or else wheel events are not passed to the parent class 344 // implementation of wheelEvent, where they are ignored and passed to the item below. 345 setEnabled(true); 346 347 connect(this, SIGNAL(notifyAnimationStartedAsync()), this, SLOT(notifyAnimationStarted()), Qt::QueuedConnection); 348} 349 350GraphicsLayerQtImpl::~GraphicsLayerQtImpl() 351{ 352 // The compositor manages lifecycle of item, so we do not want the graphicsview system to delete 353 // our items automatically. 354 const QList<QGraphicsItem*> children = childItems(); 355 QList<QGraphicsItem*>::const_iterator cit; 356 for (cit = children.constBegin(); cit != children.constEnd(); ++cit) { 357 if (QGraphicsItem* item = *cit) { 358 if (scene()) 359 scene()->removeItem(item); 360 item->setParentItem(0); 361 } 362 } 363#if ENABLE(TILED_BACKING_STORE) 364 delete m_tiledBackingStore; 365#endif 366#ifndef QT_NO_ANIMATION 367 // We do, however, own the animations. 368 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 369 for (it = m_animations.begin(); it != m_animations.end(); ++it) 370 if (QAbstractAnimation* anim = it->data()) 371 delete anim; 372#endif 373} 374 375const GraphicsLayerQtImpl* GraphicsLayerQtImpl::rootLayer() const 376{ 377 if (const GraphicsLayerQtImpl* parent = toGraphicsLayerQtImpl(parentObject())) 378 return parent->rootLayer(); 379 return this; 380} 381 382 383void GraphicsLayerQtImpl::drawLayerContent(QPainter* painter, const QRect& clipRect) 384{ 385 painter->setClipRect(clipRect, Qt::IntersectClip); 386 painter->setCompositionMode(QPainter::CompositionMode_SourceOver); 387 GraphicsContext gc(painter); 388 m_layer->paintGraphicsLayerContents(gc, clipRect); 389} 390 391QPixmap GraphicsLayerQtImpl::recache(const QRegion& regionToUpdate) 392{ 393 if (!m_layer->drawsContent() || m_size.isEmpty() || !m_size.isValid()) 394 return QPixmap(); 395 396#if ENABLE(TILED_BACKING_STORE) 397 const bool requiresTiling = (m_state.drawsContent && m_currentContent.contentType == HTMLContentType) && (m_size.width() > GRAPHICS_LAYER_TILING_THRESHOLD || m_size.height() > GRAPHICS_LAYER_TILING_THRESHOLD); 398 if (requiresTiling && !m_tiledBackingStore) { 399 m_tiledBackingStore = new TiledBackingStore(this); 400 m_tiledBackingStore->setTileCreationDelay(0); 401 setFlag(ItemUsesExtendedStyleOption, true); 402 } else if (!requiresTiling && m_tiledBackingStore) { 403 delete m_tiledBackingStore; 404 m_tiledBackingStore = 0; 405 setFlag(ItemUsesExtendedStyleOption, false); 406 } 407 408 if (m_tiledBackingStore) { 409 m_tiledBackingStore->adjustVisibleRect(); 410 const QVector<QRect> rects = regionToUpdate.rects(); 411 for (int i = 0; i < rects.size(); ++i) 412 m_tiledBackingStore->invalidate(rects[i]); 413 return QPixmap(); 414 } 415#endif 416 417 QPixmap pixmap; 418 QRegion region = regionToUpdate; 419 if (QPixmapCache::find(m_backingStore.key, &pixmap)) { 420 if (region.isEmpty()) 421 return pixmap; 422 QPixmapCache::remove(m_backingStore.key); // Remove the reference to the pixmap in the cache to avoid a detach. 423 } 424 425 { 426 bool erased = false; 427 428 // If the pixmap is not in the cache or the view has grown since last cached. 429 if (pixmap.isNull() || m_size != m_backingStore.size) { 430#if QT_DEBUG_RECACHE 431 if (pixmap.isNull()) 432 qDebug() << "CacheMiss" << this << m_size; 433#endif 434 bool fill = true; 435 QRegion newRegion; 436 QPixmap oldPixmap = pixmap; 437 438 // If the pixmap is two small to hold the view contents we enlarge, otherwise just use the old (large) pixmap. 439 if (pixmap.width() < m_size.width() || pixmap.height() < m_size.height()) { 440#if QT_DEBUG_RECACHE 441 qDebug() << "CacheGrow" << this << m_size; 442#endif 443 pixmap = QPixmap(m_size.toSize()); 444 pixmap.fill(Qt::transparent); 445 newRegion = QRegion(0, 0, m_size.width(), m_size.height()); 446 } 447 448#if 1 449 // Blit the contents of oldPixmap back into the cached pixmap as we are just adding new pixels. 450 if (!oldPixmap.isNull()) { 451 const QRegion cleanRegion = (QRegion(0, 0, m_size.width(), m_size.height()) 452 & QRegion(0, 0, m_backingStore.size.width(), m_backingStore.size.height())) - regionToUpdate; 453 if (!cleanRegion.isEmpty()) { 454#if QT_DEBUG_RECACHE 455 qDebug() << "CacheBlit" << this << cleanRegion; 456#endif 457 const QRect cleanBounds(cleanRegion.boundingRect()); 458 QPainter painter(&pixmap); 459 painter.setCompositionMode(QPainter::CompositionMode_Source); 460 painter.drawPixmap(cleanBounds.topLeft(), oldPixmap, cleanBounds); 461 newRegion -= cleanRegion; 462 fill = false; // We cannot just fill the pixmap. 463 } 464 oldPixmap = QPixmap(); 465 } 466#endif 467 region += newRegion; 468 if (fill && !region.isEmpty()) { // Clear the entire pixmap with the background. 469#if QT_DEBUG_RECACHE 470 qDebug() << "CacheErase" << this << m_size << background; 471#endif 472 erased = true; 473 pixmap.fill(Qt::transparent); 474 } 475 } 476 region &= QRegion(0, 0, m_size.width(), m_size.height()); 477 478 // If we have something to draw its time to erase it and render the contents. 479 if (!region.isEmpty()) { 480#if QT_DEBUG_CACHEDUMP 481 static int recacheCount = 0; 482 ++recacheCount; 483 qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; 484 pixmap.save(QString().sprintf("/tmp/%05d_A.png", recacheCount), "PNG"); 485#endif 486 487 QPainter painter(&pixmap); 488 GraphicsContext gc(&painter); 489 490 painter.setClipRegion(region); 491 492 if (!erased) { // Erase the area in cache that we're drawing into. 493 painter.setCompositionMode(QPainter::CompositionMode_Clear); 494 painter.fillRect(region.boundingRect(), Qt::transparent); 495 496#if QT_DEBUG_CACHEDUMP 497 qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; 498 pixmap.save(QString().sprintf("/tmp/%05d_B.png", recacheCount), "PNG"); 499#endif 500 } 501 502 // Render the actual contents into the cache. 503 painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 504 m_layer->paintGraphicsLayerContents(gc, region.boundingRect()); 505 painter.end(); 506 507#if QT_DEBUG_CACHEDUMP 508 qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; 509 pixmap.save(QString().sprintf("/tmp/%05d_C.png", recacheCount), "PNG"); 510#endif 511 } 512 m_backingStore.size = m_size; // Store the used size of the pixmap. 513 } 514 515 // Finally insert into the cache and allow a reference there. 516 m_backingStore.key = QPixmapCache::insert(pixmap); 517 return pixmap; 518} 519 520void GraphicsLayerQtImpl::updateTransform() 521{ 522 if (!m_transformAnimationRunning) 523 m_baseTransform = m_layer->transform(); 524 525 TransformationMatrix localTransform; 526 527 GraphicsLayerQtImpl* parent = toGraphicsLayerQtImpl(parentObject()); 528 529 // WebCore has relative-to-size originPoint, where as the QGraphicsView has a pixel originPoint. 530 // Thus, we need to convert here as we have to manage this outselves due to the fact that the 531 // transformOrigin of the graphicsview is imcompatible. 532 const qreal originX = m_state.anchorPoint.x() * m_size.width(); 533 const qreal originY = m_state.anchorPoint.y() * m_size.height(); 534 535 // We ignore QGraphicsItem::pos completely, and use transforms only, due to the fact that we 536 // have to maintain that ourselves for 3D. 537 localTransform 538 .translate3d(originX + m_state.pos.x(), originY + m_state.pos.y(), m_state.anchorPoint.z()) 539 .multiply(m_baseTransform) 540 .translate3d(-originX, -originY, -m_state.anchorPoint.z()); 541 542 // This is the actual 3D transform of this item, with the ancestors' transform baked in. 543 m_transformRelativeToRootLayer = TransformationMatrix(parent ? parent->m_transformRelativeToRootLayer : TransformationMatrix()) 544 .multiply(localTransform); 545 546 // Now we have enough information to determine if the layer is facing backwards. 547 if (!m_state.backfaceVisibility && m_transformRelativeToRootLayer.inverse().m33() < 0) { 548 setVisible(false); 549 // No point in making extra calculations for invisible elements. 550 return; 551 } 552 553 // The item is front-facing or backface-visibility is on. 554 setVisible(true); 555 556 // Flatten to 2D-space of this item if it doesn't preserve 3D. 557 if (!m_state.preserves3D) { 558 m_transformRelativeToRootLayer.setM13(0); 559 m_transformRelativeToRootLayer.setM23(0); 560 m_transformRelativeToRootLayer.setM31(0); 561 m_transformRelativeToRootLayer.setM32(0); 562 m_transformRelativeToRootLayer.setM33(1); 563 m_transformRelativeToRootLayer.setM34(0); 564 m_transformRelativeToRootLayer.setM43(0); 565 } 566 567 // Apply perspective for the use of this item's children. Perspective is always applied from the item's 568 // center. 569 if (!m_state.childrenTransform.isIdentity()) { 570 m_transformRelativeToRootLayer 571 .translate(m_size.width() / 2, m_size.height() /2) 572 .multiply(m_state.childrenTransform) 573 .translate(-m_size.width() / 2, -m_size.height() /2); 574 } 575 576 bool inverseOk = true; 577 // Use QTransform::inverse to extrapolate the relative transform of this item, based on the parent's 578 // transform relative to the root layer and the desired transform for this item relative to the root layer. 579 const QTransform parentTransform = parent ? parent->itemTransform(rootLayer()) : QTransform(); 580 const QTransform transform2D = QTransform(m_transformRelativeToRootLayer) * parentTransform.inverted(&inverseOk); 581 582 // In rare cases the transformation cannot be inversed - in that case we don't apply the transformation at 583 // all, otherwise we'd flicker. FIXME: This should be amended when Qt moves to a real 3D scene-graph. 584 if (!inverseOk) 585 return; 586 587 setTransform(transform2D); 588 589 const QList<QGraphicsItem*> children = childItems(); 590 QList<QGraphicsItem*>::const_iterator it; 591 for (it = children.constBegin(); it != children.constEnd(); ++it) 592 if (GraphicsLayerQtImpl* layer= toGraphicsLayerQtImpl(*it)) 593 layer->updateTransform(); 594} 595 596void GraphicsLayerQtImpl::setBaseTransform(const TransformationMatrix& baseTransform) 597{ 598 m_baseTransform = baseTransform; 599 updateTransform(); 600} 601 602QPainterPath GraphicsLayerQtImpl::opaqueArea() const 603{ 604 QPainterPath painterPath; 605 606 // We try out best to return the opaque area, maybe it will help graphics-view render less items. 607 if (m_currentContent.backgroundColor.isValid() && m_currentContent.backgroundColor.alpha() == 0xff) 608 painterPath.addRect(boundingRect()); 609 else { 610 if (m_state.contentsOpaque 611 || (m_currentContent.contentType == ColorContentType && m_currentContent.contentsBackgroundColor.alpha() == 0xff) 612 || (m_currentContent.contentType == MediaContentType) 613 || (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlpha())) { 614 painterPath.addRect(m_state.contentsRect); 615 } 616 } 617 return painterPath; 618} 619 620QRectF GraphicsLayerQtImpl::boundingRect() const 621{ 622 return QRectF(QPointF(0, 0), QSizeF(m_size)); 623} 624 625void GraphicsLayerQtImpl::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) 626{ 627#if ENABLE(TILED_BACKING_STORE) 628 // FIXME: There's currently no Qt API to know if a new region of an item is exposed outside of the paint event. 629 // Suggested for Qt: http://bugreports.qt.nokia.com/browse/QTBUG-14877. 630 if (m_tiledBackingStore) 631 m_tiledBackingStore->adjustVisibleRect(); 632#endif 633 634 if (m_currentContent.backgroundColor.isValid()) 635 painter->fillRect(option->exposedRect, QColor(m_currentContent.backgroundColor)); 636 637 switch (m_currentContent.contentType) { 638 case HTMLContentType: 639 if (m_state.drawsContent) { 640 if (!allowAcceleratedCompositingCache()) 641 drawLayerContent(painter, option->exposedRect.toRect()); 642 else { 643 QPixmap backingStore; 644 // We might need to recache, in case we try to paint and the cache was purged (e.g. if it was full). 645 if (!QPixmapCache::find(m_backingStore.key, &backingStore) || backingStore.size() != m_size.toSize()) 646 backingStore = recache(QRegion(m_state.contentsRect)); 647 const QRectF bounds(0, 0, m_backingStore.size.width(), m_backingStore.size.height()); 648 painter->drawPixmap(0, 0, backingStore); 649 } 650 } 651 break; 652 case PixmapContentType: 653 painter->drawPixmap(m_state.contentsRect, m_currentContent.pixmap); 654 break; 655 case ColorContentType: 656 painter->fillRect(m_state.contentsRect, m_currentContent.contentsBackgroundColor); 657 break; 658 case MediaContentType: 659 // we don't need to paint anything: we have a QGraphicsItem from the media element 660 break; 661 } 662} 663 664void GraphicsLayerQtImpl::notifySyncRequired() 665{ 666 m_blockNotifySyncRequired = false; 667 668 if (m_layer->client()) 669 m_layer->client()->notifySyncRequired(m_layer); 670} 671 672void GraphicsLayerQtImpl::notifyChange(ChangeMask changeMask) 673{ 674 m_changeMask |= changeMask; 675 676 if (m_blockNotifySyncRequired) 677 return; 678 679 static QMetaMethod syncMethod = staticMetaObject.method(staticMetaObject.indexOfMethod("notifySyncRequired()")); 680 syncMethod.invoke(this, Qt::QueuedConnection); 681 682 m_blockNotifySyncRequired = true; 683} 684 685void GraphicsLayerQtImpl::flushChanges(bool recursive, bool forceUpdateTransform) 686{ 687 // This is the bulk of the work. understanding what the compositor is trying to achieve, what 688 // graphicsview can do, and trying to find a sane common-ground. 689 if (!m_layer || m_changeMask == NoChanges) 690 goto afterLayerChanges; 691 692 if (m_changeMask & ParentChange) { 693 // The WebCore compositor manages item ownership. We have to make sure graphicsview doesn't 694 // try to snatch that ownership. 695 if (!m_layer->parent() && !parentItem()) 696 setParentItem(0); 697 else if (m_layer && m_layer->parent() && m_layer->parent()->platformLayer() != parentItem()) 698 setParentItem(m_layer->parent()->platformLayer()); 699 } 700 701 if (m_changeMask & ChildrenChange) { 702 // We basically do an XOR operation on the list of current children and the list of wanted 703 // children, and remove/add. 704 QSet<QGraphicsItem*> newChildren; 705 const Vector<GraphicsLayer*> newChildrenVector = (m_layer->children()); 706 newChildren.reserve(newChildrenVector.size()); 707 708 for (size_t i = 0; i < newChildrenVector.size(); ++i) 709 newChildren.insert(newChildrenVector[i]->platformLayer()); 710 711 const QSet<QGraphicsItem*> currentChildren = childItems().toSet(); 712 const QSet<QGraphicsItem*> childrenToAdd = newChildren - currentChildren; 713 const QSet<QGraphicsItem*> childrenToRemove = currentChildren - newChildren; 714 715 QSet<QGraphicsItem*>::const_iterator it; 716 for (it = childrenToAdd.constBegin(); it != childrenToAdd.constEnd(); ++it) { 717 if (QGraphicsItem* w = *it) 718 w->setParentItem(this); 719 } 720 721 QSet<QGraphicsItem*>::const_iterator rit; 722 for (rit = childrenToRemove.constBegin(); rit != childrenToRemove.constEnd(); ++rit) { 723 if (GraphicsLayerQtImpl* w = toGraphicsLayerQtImpl(*rit)) 724 w->setParentItem(0); 725 } 726 727 // Children are ordered by z-value, let graphicsview know. 728 for (size_t i = 0; i < newChildrenVector.size(); ++i) { 729 if (newChildrenVector[i]->platformLayer()) 730 newChildrenVector[i]->platformLayer()->setZValue(i); 731 } 732 } 733 734 if (m_changeMask & MaskLayerChange) { 735 // We can't paint here, because we don't know if the mask layer itself is ready... we'll have 736 // to wait till this layer tries to paint. 737 setFlag(ItemClipsChildrenToShape, m_layer->maskLayer() || m_layer->masksToBounds()); 738#ifndef QT_NO_GRAPHICSEFFECT 739 setGraphicsEffect(0); 740 if (m_layer->maskLayer()) { 741 if (GraphicsLayerQtImpl* mask = toGraphicsLayerQtImpl(m_layer->maskLayer()->platformLayer())) { 742 mask->m_maskEffect = new MaskEffectQt(this, mask); 743 setGraphicsEffect(mask->m_maskEffect.data()); 744 } 745 } 746#endif 747 } 748 749 if (m_changeMask & SizeChange) { 750 if (m_layer->size() != m_state.size) { 751 prepareGeometryChange(); 752 m_size = QSizeF(m_layer->size().width(), m_layer->size().height()); 753 } 754 } 755 756 // FIXME: This is a hack, due to a probable QGraphicsScene bug when rapidly modifying the perspective 757 // but without this line we get graphic artifacts. 758 if ((m_changeMask & ChildrenTransformChange) && m_state.childrenTransform != m_layer->childrenTransform()) 759 if (scene()) 760 scene()->update(); 761 762 if (m_changeMask & (ChildrenTransformChange | Preserves3DChange | TransformChange | AnchorPointChange | SizeChange | BackfaceVisibilityChange | PositionChange | ParentChange)) { 763 // Due to the differences between the way WebCore handles transforms and the way Qt handles transforms, 764 // all these elements affect the transforms of all the descendants. 765 forceUpdateTransform = true; 766 } 767 768 if (m_changeMask & (ContentChange | DrawsContentChange | MaskLayerChange)) { 769 switch (m_pendingContent.contentType) { 770 case PixmapContentType: 771 update(); 772 setFlag(ItemHasNoContents, false); 773 break; 774 775 case MediaContentType: 776 setFlag(ItemHasNoContents, true); 777 m_pendingContent.mediaLayer.data()->setParentItem(this); 778 break; 779 780 case ColorContentType: 781 if (m_pendingContent.contentType != m_currentContent.contentType 782 || m_pendingContent.contentsBackgroundColor != m_currentContent.contentsBackgroundColor) 783 update(); 784 m_state.drawsContent = false; 785 setFlag(ItemHasNoContents, false); 786 787 // Only use ItemUsesExtendedStyleOption for HTML content as colors don't gain much from that. 788 setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, false); 789 break; 790 791 case HTMLContentType: 792 if (m_pendingContent.contentType != m_currentContent.contentType) 793 update(); 794 if (!m_state.drawsContent && m_layer->drawsContent()) 795 update(); 796 797 setFlag(ItemHasNoContents, !m_layer->drawsContent()); 798 break; 799 } 800 } 801 802 if ((m_changeMask & OpacityChange) && m_state.opacity != m_layer->opacity() && !m_opacityAnimationRunning) 803 setOpacity(m_layer->opacity()); 804 805 if (m_changeMask & ContentsRectChange) { 806 const QRect rect(m_layer->contentsRect()); 807 if (m_state.contentsRect != rect) { 808 m_state.contentsRect = rect; 809 if (m_pendingContent.mediaLayer) { 810 QGraphicsWidget* widget = qobject_cast<QGraphicsWidget*>(m_pendingContent.mediaLayer.data()); 811 if (widget) 812 widget->setGeometry(rect); 813 } 814 update(); 815 } 816 } 817 818 if ((m_changeMask & MasksToBoundsChange) && m_state.masksToBounds != m_layer->masksToBounds()) { 819 setFlag(QGraphicsItem::ItemClipsToShape, m_layer->masksToBounds()); 820 setFlag(QGraphicsItem::ItemClipsChildrenToShape, m_layer->masksToBounds()); 821 } 822 823 if ((m_changeMask & ContentsOpaqueChange) && m_state.contentsOpaque != m_layer->contentsOpaque()) 824 prepareGeometryChange(); 825 826#ifndef QT_NO_GRAPHICSEFFECT 827 if (m_maskEffect) 828 m_maskEffect.data()->update(); 829 else 830#endif 831 if (m_changeMask & DisplayChange) { 832#ifndef QT_GRAPHICS_LAYER_NO_RECACHE_ON_DISPLAY_CHANGE 833 // Recache now: all the content is ready and we don't want to wait until the paint event. 834 // We only need to do this for HTML content, there's no point in caching directly composited 835 // content like images or solid rectangles. 836 if (m_pendingContent.contentType == HTMLContentType && allowAcceleratedCompositingCache()) 837 recache(m_pendingContent.regionToUpdate); 838#endif 839 update(m_pendingContent.regionToUpdate.boundingRect()); 840 m_pendingContent.regionToUpdate = QRegion(); 841 } 842 843 if ((m_changeMask & BackgroundColorChange) 844 && (m_pendingContent.backgroundColor != m_currentContent.backgroundColor)) 845 update(); 846 847 m_state.maskLayer = m_layer->maskLayer(); 848 m_state.pos = m_layer->position(); 849 m_state.anchorPoint = m_layer->anchorPoint(); 850 m_state.size = m_layer->size(); 851 m_state.transform = m_layer->transform(); 852 m_state.contentsOrientation =m_layer->contentsOrientation(); 853 m_state.opacity = m_layer->opacity(); 854 m_state.contentsRect = m_layer->contentsRect(); 855 m_state.preserves3D = m_layer->preserves3D(); 856 m_state.masksToBounds = m_layer->masksToBounds(); 857 m_state.drawsContent = m_layer->drawsContent(); 858 m_state.contentsOpaque = m_layer->contentsOpaque(); 859 m_state.backfaceVisibility = m_layer->backfaceVisibility(); 860 m_state.childrenTransform = m_layer->childrenTransform(); 861 m_currentContent.pixmap = m_pendingContent.pixmap; 862 m_currentContent.contentType = m_pendingContent.contentType; 863 m_currentContent.mediaLayer = m_pendingContent.mediaLayer; 864 m_currentContent.backgroundColor = m_pendingContent.backgroundColor; 865 m_currentContent.contentsBackgroundColor = m_pendingContent.contentsBackgroundColor; 866 m_pendingContent.regionToUpdate = QRegion(); 867 m_changeMask = NoChanges; 868 869afterLayerChanges: 870 if (forceUpdateTransform) 871 updateTransform(); 872 873 if (!recursive) 874 return; 875 876 QList<QGraphicsItem*> children = childItems(); 877 if (m_state.maskLayer) 878 children.append(m_state.maskLayer->platformLayer()); 879 880 QList<QGraphicsItem*>::const_iterator it; 881 for (it = children.constBegin(); it != children.constEnd(); ++it) { 882 if (QGraphicsItem* item = *it) { 883 if (GraphicsLayerQtImpl* layer = toGraphicsLayerQtImpl(item)) 884 layer->flushChanges(true, forceUpdateTransform); 885 } 886 } 887} 888 889#if ENABLE(TILED_BACKING_STORE) 890/* \reimp (TiledBackingStoreClient.h) 891*/ 892void GraphicsLayerQtImpl::tiledBackingStorePaintBegin() 893{ 894} 895 896/* \reimp (TiledBackingStoreClient.h) 897*/ 898void GraphicsLayerQtImpl::tiledBackingStorePaint(GraphicsContext* gc, const IntRect& rect) 899{ 900 m_layer->paintGraphicsLayerContents(*gc, rect); 901} 902 903/* \reimp (TiledBackingStoreClient.h) 904*/ 905void GraphicsLayerQtImpl::tiledBackingStorePaintEnd(const Vector<IntRect>& paintedArea) 906{ 907 for (int i = 0; i < paintedArea.size(); ++i) 908 update(QRectF(paintedArea[i])); 909} 910 911/* \reimp (TiledBackingStoreClient.h) 912*/ 913IntRect GraphicsLayerQtImpl::tiledBackingStoreContentsRect() 914{ 915 return m_layer->contentsRect(); 916} 917 918/* \reimp (TiledBackingStoreClient.h) 919*/ 920Color GraphicsLayerQtImpl::tiledBackingStoreBackgroundColor() const 921{ 922 if (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlphaChannel()) 923 return Color(0, 0, 0); 924 // We return a transparent color so that the tiles initialize with alpha. 925 return Color(0, 0, 0, 0); 926} 927 928IntRect GraphicsLayerQtImpl::tiledBackingStoreVisibleRect() 929{ 930 const QGraphicsView* view = scene()->views().isEmpty() ? 0 : scene()->views().first(); 931 if (!view) 932 return mapFromScene(scene()->sceneRect()).boundingRect().toAlignedRect(); 933 934 // All we get is the viewport's visible region. We have to map it to the scene and then to item coordinates. 935 return mapFromScene(view->mapToScene(view->viewport()->visibleRegion().boundingRect()).boundingRect()).boundingRect().toAlignedRect(); 936} 937#endif 938 939void GraphicsLayerQtImpl::notifyAnimationStarted() 940{ 941 // WebCore notifies javascript when the animation starts. Here we're letting it know. 942 m_layer->client()->notifyAnimationStarted(m_layer, /* DOM time */ WTF::currentTime()); 943} 944 945GraphicsLayerQt::GraphicsLayerQt(GraphicsLayerClient* client) 946 : GraphicsLayer(client) 947 , m_impl(PassOwnPtr<GraphicsLayerQtImpl>(new GraphicsLayerQtImpl(this))) 948{ 949} 950 951GraphicsLayerQt::~GraphicsLayerQt() 952{ 953} 954 955// This is the hook for WebCore compositor to know that Qt implements compositing with GraphicsLayerQt. 956PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client) 957{ 958 return new GraphicsLayerQt(client); 959} 960 961/* \reimp (GraphicsLayer.h): The current size might change, thus we need to update the whole display. 962*/ 963void GraphicsLayerQt::setNeedsDisplay() 964{ 965 m_impl->m_pendingContent.regionToUpdate = QRegion(QRect(QPoint(0, 0), QSize(size().width(), size().height()))); 966 m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange); 967} 968 969/* \reimp (GraphicsLayer.h) 970*/ 971void GraphicsLayerQt::setNeedsDisplayInRect(const FloatRect& rect) 972{ 973 m_impl->m_pendingContent.regionToUpdate |= QRectF(rect).toAlignedRect(); 974 m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange); 975} 976 977void GraphicsLayerQt::setContentsNeedsDisplay() 978{ 979 switch (m_impl->m_pendingContent.contentType) { 980 case GraphicsLayerQtImpl::MediaContentType: 981 if (!m_impl->m_pendingContent.mediaLayer) 982 return; 983 m_impl->m_pendingContent.mediaLayer.data()->update(); 984 break; 985 default: 986 setNeedsDisplay(); 987 break; 988 } 989} 990 991/* \reimp (GraphicsLayer.h) 992*/ 993void GraphicsLayerQt::setName(const String& name) 994{ 995 m_impl->setObjectName(name); 996 GraphicsLayer::setName(name); 997} 998 999/* \reimp (GraphicsLayer.h) 1000*/ 1001void GraphicsLayerQt::setParent(GraphicsLayer* layer) 1002{ 1003 m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange); 1004 GraphicsLayer::setParent(layer); 1005} 1006 1007/* \reimp (GraphicsLayer.h) 1008*/ 1009bool GraphicsLayerQt::setChildren(const Vector<GraphicsLayer*>& children) 1010{ 1011 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1012 return GraphicsLayer::setChildren(children); 1013} 1014 1015/* \reimp (GraphicsLayer.h) 1016*/ 1017void GraphicsLayerQt::addChild(GraphicsLayer* layer) 1018{ 1019 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1020 GraphicsLayer::addChild(layer); 1021} 1022 1023/* \reimp (GraphicsLayer.h) 1024*/ 1025void GraphicsLayerQt::addChildAtIndex(GraphicsLayer* layer, int index) 1026{ 1027 GraphicsLayer::addChildAtIndex(layer, index); 1028 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1029} 1030 1031/* \reimp (GraphicsLayer.h) 1032*/ 1033void GraphicsLayerQt::addChildAbove(GraphicsLayer* layer, GraphicsLayer* sibling) 1034{ 1035 GraphicsLayer::addChildAbove(layer, sibling); 1036 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1037} 1038 1039/* \reimp (GraphicsLayer.h) 1040*/ 1041void GraphicsLayerQt::addChildBelow(GraphicsLayer* layer, GraphicsLayer* sibling) 1042{ 1043 1044 GraphicsLayer::addChildBelow(layer, sibling); 1045 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1046} 1047 1048/* \reimp (GraphicsLayer.h) 1049*/ 1050bool GraphicsLayerQt::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild) 1051{ 1052 if (GraphicsLayer::replaceChild(oldChild, newChild)) { 1053 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); 1054 return true; 1055 } 1056 1057 return false; 1058} 1059 1060/* \reimp (GraphicsLayer.h) 1061*/ 1062void GraphicsLayerQt::removeFromParent() 1063{ 1064 if (parent()) 1065 m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange); 1066 GraphicsLayer::removeFromParent(); 1067} 1068 1069/* \reimp (GraphicsLayer.h) 1070*/ 1071void GraphicsLayerQt::setMaskLayer(GraphicsLayer* value) 1072{ 1073 if (value == maskLayer()) 1074 return; 1075 GraphicsLayer::setMaskLayer(value); 1076 m_impl->notifyChange(GraphicsLayerQtImpl::MaskLayerChange); 1077} 1078 1079/* \reimp (GraphicsLayer.h) 1080*/ 1081void GraphicsLayerQt::setPosition(const FloatPoint& value) 1082{ 1083 if (value == position()) 1084 return; 1085 GraphicsLayer::setPosition(value); 1086 m_impl->notifyChange(GraphicsLayerQtImpl::PositionChange); 1087} 1088 1089/* \reimp (GraphicsLayer.h) 1090*/ 1091void GraphicsLayerQt::setAnchorPoint(const FloatPoint3D& value) 1092{ 1093 if (value == anchorPoint()) 1094 return; 1095 GraphicsLayer::setAnchorPoint(value); 1096 m_impl->notifyChange(GraphicsLayerQtImpl::AnchorPointChange); 1097} 1098 1099/* \reimp (GraphicsLayer.h) 1100*/ 1101void GraphicsLayerQt::setSize(const FloatSize& value) 1102{ 1103 if (value == size()) 1104 return; 1105 GraphicsLayer::setSize(value); 1106 m_impl->notifyChange(GraphicsLayerQtImpl::SizeChange); 1107} 1108 1109/* \reimp (GraphicsLayer.h) 1110*/ 1111void GraphicsLayerQt::setTransform(const TransformationMatrix& value) 1112{ 1113 if (value == transform()) 1114 return; 1115 GraphicsLayer::setTransform(value); 1116 m_impl->notifyChange(GraphicsLayerQtImpl::TransformChange); 1117} 1118 1119/* \reimp (GraphicsLayer.h) 1120*/ 1121void GraphicsLayerQt::setChildrenTransform(const TransformationMatrix& value) 1122{ 1123 if (value == childrenTransform()) 1124 return; 1125 GraphicsLayer::setChildrenTransform(value); 1126 m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenTransformChange); 1127} 1128 1129/* \reimp (GraphicsLayer.h) 1130*/ 1131void GraphicsLayerQt::setPreserves3D(bool value) 1132{ 1133 if (value == preserves3D()) 1134 return; 1135 GraphicsLayer::setPreserves3D(value); 1136 m_impl->notifyChange(GraphicsLayerQtImpl::Preserves3DChange); 1137} 1138 1139/* \reimp (GraphicsLayer.h) 1140*/ 1141void GraphicsLayerQt::setMasksToBounds(bool value) 1142{ 1143 if (value == masksToBounds()) 1144 return; 1145 GraphicsLayer::setMasksToBounds(value); 1146 m_impl->notifyChange(GraphicsLayerQtImpl::MasksToBoundsChange); 1147} 1148 1149/* \reimp (GraphicsLayer.h) 1150*/ 1151void GraphicsLayerQt::setDrawsContent(bool value) 1152{ 1153 if (value == drawsContent()) 1154 return; 1155 m_impl->notifyChange(GraphicsLayerQtImpl::DrawsContentChange); 1156 GraphicsLayer::setDrawsContent(value); 1157} 1158 1159/* \reimp (GraphicsLayer.h) 1160*/ 1161void GraphicsLayerQt::setBackgroundColor(const Color& value) 1162{ 1163 if (value == m_impl->m_pendingContent.backgroundColor) 1164 return; 1165 m_impl->m_pendingContent.backgroundColor = value; 1166 GraphicsLayer::setBackgroundColor(value); 1167 m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); 1168} 1169 1170/* \reimp (GraphicsLayer.h) 1171*/ 1172void GraphicsLayerQt::clearBackgroundColor() 1173{ 1174 if (!m_impl->m_pendingContent.backgroundColor.isValid()) 1175 return; 1176 m_impl->m_pendingContent.backgroundColor = QColor(); 1177 GraphicsLayer::clearBackgroundColor(); 1178 m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); 1179} 1180 1181/* \reimp (GraphicsLayer.h) 1182*/ 1183void GraphicsLayerQt::setContentsOpaque(bool value) 1184{ 1185 if (value == contentsOpaque()) 1186 return; 1187 m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOpaqueChange); 1188 GraphicsLayer::setContentsOpaque(value); 1189} 1190 1191/* \reimp (GraphicsLayer.h) 1192*/ 1193void GraphicsLayerQt::setBackfaceVisibility(bool value) 1194{ 1195 if (value == backfaceVisibility()) 1196 return; 1197 GraphicsLayer::setBackfaceVisibility(value); 1198 m_impl->notifyChange(GraphicsLayerQtImpl::BackfaceVisibilityChange); 1199} 1200 1201/* \reimp (GraphicsLayer.h) 1202*/ 1203void GraphicsLayerQt::setOpacity(float value) 1204{ 1205 if (value == opacity()) 1206 return; 1207 GraphicsLayer::setOpacity(value); 1208 m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange); 1209} 1210 1211/* \reimp (GraphicsLayer.h) 1212*/ 1213void GraphicsLayerQt::setContentsRect(const IntRect& value) 1214{ 1215 if (value == contentsRect()) 1216 return; 1217 GraphicsLayer::setContentsRect(value); 1218 m_impl->notifyChange(GraphicsLayerQtImpl::ContentsRectChange); 1219} 1220 1221/* \reimp (GraphicsLayer.h) 1222*/ 1223void GraphicsLayerQt::setContentsToImage(Image* image) 1224{ 1225 m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); 1226 m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType; 1227 GraphicsLayer::setContentsToImage(image); 1228 if (image) { 1229 QPixmap* pxm = image->nativeImageForCurrentFrame(); 1230 if (pxm) { 1231 m_impl->m_pendingContent.pixmap = *pxm; 1232 m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::PixmapContentType; 1233 return; 1234 } 1235 } 1236 m_impl->m_pendingContent.pixmap = QPixmap(); 1237} 1238 1239/* \reimp (GraphicsLayer.h) 1240*/ 1241void GraphicsLayerQt::setContentsBackgroundColor(const Color& color) 1242{ 1243 m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); 1244 m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::ColorContentType; 1245 m_impl->m_pendingContent.contentsBackgroundColor = QColor(color); 1246 GraphicsLayer::setContentsBackgroundColor(color); 1247} 1248 1249void GraphicsLayerQt::setContentsToMedia(PlatformLayer* media) 1250{ 1251 if (media) { 1252 m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::MediaContentType; 1253 m_impl->m_pendingContent.mediaLayer = media->toGraphicsObject(); 1254 } else 1255 m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType; 1256 1257 m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); 1258 GraphicsLayer::setContentsToMedia(media); 1259} 1260 1261void GraphicsLayerQt::setContentsToCanvas(PlatformLayer* canvas) 1262{ 1263 setContentsToMedia(canvas); 1264} 1265 1266/* \reimp (GraphicsLayer.h) 1267*/ 1268void GraphicsLayerQt::setContentsOrientation(CompositingCoordinatesOrientation orientation) 1269{ 1270 m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOrientationChange); 1271 GraphicsLayer::setContentsOrientation(orientation); 1272} 1273 1274/* \reimp (GraphicsLayer.h) 1275*/ 1276void GraphicsLayerQt::distributeOpacity(float o) 1277{ 1278 m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange); 1279 m_impl->m_state.distributeOpacity = true; 1280} 1281 1282/* \reimp (GraphicsLayer.h) 1283*/ 1284float GraphicsLayerQt::accumulatedOpacity() const 1285{ 1286 return m_impl->effectiveOpacity(); 1287} 1288 1289/* \reimp (GraphicsLayer.h) 1290*/ 1291void GraphicsLayerQt::syncCompositingState() 1292{ 1293 m_impl->flushChanges(); 1294 GraphicsLayer::syncCompositingState(); 1295} 1296 1297/* \reimp (GraphicsLayer.h) 1298*/ 1299void GraphicsLayerQt::syncCompositingStateForThisLayerOnly() 1300{ 1301 // We can't call flushChanges recursively here 1302 m_impl->flushChanges(false); 1303 GraphicsLayer::syncCompositingStateForThisLayerOnly(); 1304} 1305 1306/* \reimp (GraphicsLayer.h) 1307*/ 1308PlatformLayer* GraphicsLayerQt::platformLayer() const 1309{ 1310 return m_impl.get(); 1311} 1312 1313// Now we start dealing with WebCore animations translated to Qt animations 1314 1315template <typename T> 1316struct KeyframeValueQt { 1317 const TimingFunction* timingFunction; 1318 T value; 1319}; 1320 1321/* Copied from AnimationBase.cpp 1322*/ 1323static inline double solveEpsilon(double duration) 1324{ 1325 return 1.0 / (200.0 * duration); 1326} 1327 1328static inline double solveCubicBezierFunction(qreal p1x, qreal p1y, qreal p2x, qreal p2y, double t, double duration) 1329{ 1330 UnitBezier bezier(p1x, p1y, p2x, p2y); 1331 return bezier.solve(t, solveEpsilon(duration)); 1332} 1333 1334static inline double solveStepsFunction(int numSteps, bool stepAtStart, double t) 1335{ 1336 if (stepAtStart) 1337 return qMin(1.0, (floor(numSteps * t) + 1) / numSteps); 1338 return floor(numSteps * t) / numSteps; 1339} 1340 1341static inline qreal applyTimingFunction(const TimingFunction* timingFunction, qreal progress, double duration) 1342{ 1343 // We want the timing function to be as close as possible to what the web-developer intended, so 1344 // we're using the same function used by WebCore when compositing is disabled. Using easing-curves 1345 // would probably work for some of the cases, but wouldn't really buy us anything as we'd have to 1346 // convert the bezier function back to an easing curve. 1347 1348 if (timingFunction->isCubicBezierTimingFunction()) { 1349 const CubicBezierTimingFunction* ctf = static_cast<const CubicBezierTimingFunction*>(timingFunction); 1350 return solveCubicBezierFunction(ctf->x1(), 1351 ctf->y1(), 1352 ctf->x2(), 1353 ctf->y2(), 1354 double(progress), double(duration) / 1000); 1355 } else if (timingFunction->isStepsTimingFunction()) { 1356 const StepsTimingFunction* stf = static_cast<const StepsTimingFunction*>(timingFunction); 1357 return solveStepsFunction(stf->numberOfSteps(), stf->stepAtStart(), double(progress)); 1358 } else 1359 return progress; 1360} 1361 1362// Helper functions to safely get a value out of WebCore's AnimationValue*. 1363 1364#ifndef QT_NO_ANIMATION 1365static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, TransformOperations& transformOperations) 1366{ 1367 transformOperations = TransformOperations(); 1368 if (!animationValue) 1369 return; 1370 1371 if (const TransformOperations* ops = static_cast<const TransformAnimationValue*>(animationValue)->value()) 1372 transformOperations = *ops; 1373} 1374 1375static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, qreal& realValue) 1376{ 1377 realValue = animationValue ? static_cast<const FloatAnimationValue*>(animationValue)->value() : 0; 1378} 1379 1380// We put a bit of the functionality in a base class to allow casting and to save some code size. 1381 1382class AnimationQtBase : public QAbstractAnimation { 1383public: 1384 AnimationQtBase(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) 1385 : QAbstractAnimation(0) 1386 , m_layer(layer) 1387 , m_boxSize(boxSize) 1388 , m_duration(anim->duration() * 1000) 1389 , m_isAlternate(anim->direction() == Animation::AnimationDirectionAlternate) 1390 , m_webkitPropertyID(values.property()) 1391 , m_webkitAnimation(anim) 1392 , m_keyframesName(name) 1393 , m_fillsForwards(false) 1394 { 1395 } 1396 1397 virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) 1398 { 1399 QAbstractAnimation::updateState(newState, oldState); 1400 1401 // For some reason we have do this asynchronously - or the animation won't work. 1402 if (newState == Running && oldState == Stopped && m_layer.data()) 1403 m_layer.data()->notifyAnimationStartedAsync(); 1404 } 1405 1406 virtual int duration() const { return m_duration; } 1407 1408 QWeakPointer<GraphicsLayerQtImpl> m_layer; 1409 IntSize m_boxSize; 1410 int m_duration; 1411 bool m_isAlternate; 1412 AnimatedPropertyID m_webkitPropertyID; 1413 1414 // We might need this in case the same animation is added again (i.e. resumed by WebCore). 1415 const Animation* m_webkitAnimation; 1416 QString m_keyframesName; 1417 bool m_fillsForwards; 1418}; 1419 1420// We'd rather have a templatized QAbstractAnimation than QPropertyAnimation / QVariantAnimation; 1421// Since we know the types that we're dealing with, the QObject/QProperty/QVariant abstraction 1422// buys us very little in this case, for too much overhead. 1423template <typename T> 1424class AnimationQt : public AnimationQtBase { 1425 1426public: 1427 AnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) 1428 : AnimationQtBase(layer, values, boxSize, anim, name) 1429 { 1430 // Copying those WebCore structures is not trivial, we have to do it like this. 1431 for (size_t i = 0; i < values.size(); ++i) { 1432 const AnimationValue* animationValue = values.at(i); 1433 KeyframeValueQt<T> keyframeValue; 1434 if (animationValue->timingFunction()) 1435 keyframeValue.timingFunction = animationValue->timingFunction(); 1436 else 1437 keyframeValue.timingFunction = anim->timingFunction().get(); 1438 webkitAnimationToQtAnimationValue(animationValue, keyframeValue.value); 1439 m_keyframeValues[animationValue->keyTime()] = keyframeValue; 1440 } 1441 } 1442 1443protected: 1444 1445 // This is the part that differs between animated properties. 1446 virtual void applyFrame(const T& fromValue, const T& toValue, qreal progress) = 0; 1447 1448 virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) 1449 { 1450#if QT_DEBUG_FPS 1451 if (newState == Running && oldState == Stopped) { 1452 qDebug("Animation Started!"); 1453 m_fps.frames = 0; 1454 m_fps.duration.start(); 1455 } else if (newState == Stopped && oldState == Running) { 1456 const int duration = m_fps.duration.elapsed(); 1457 qDebug("Animation Ended! %dms [%f FPS]", duration, 1458 (1000 / (((float)duration) / m_fps.frames))); 1459 } 1460#endif 1461 AnimationQtBase::updateState(newState, oldState); 1462 } 1463 1464 virtual void updateCurrentTime(int currentTime) 1465 { 1466 if (!m_layer) 1467 return; 1468 1469 qreal progress = qreal(currentLoopTime()) / duration(); 1470 1471 if (m_isAlternate && currentLoop()%2) 1472 progress = 1-progress; 1473 1474 if (m_keyframeValues.isEmpty()) 1475 return; 1476 1477 // Find the current from-to keyframes in our little map. 1478 typename QMap<qreal, KeyframeValueQt<T> >::iterator it = m_keyframeValues.find(progress); 1479 1480 // We didn't find an exact match, we try the closest match (lower bound). 1481 if (it == m_keyframeValues.end()) 1482 it = m_keyframeValues.lowerBound(progress)-1; 1483 1484 // We didn't find any match; use the first keyframe. 1485 if (it == m_keyframeValues.end()) 1486 it = m_keyframeValues.begin(); 1487 1488 typename QMap<qreal, KeyframeValueQt<T> >::iterator it2 = it + 1; 1489 if (it2 == m_keyframeValues.end()) 1490 it2 = it; 1491 const KeyframeValueQt<T>& fromKeyframe = it.value(); 1492 const KeyframeValueQt<T>& toKeyframe = it2.value(); 1493 1494 const TimingFunction* timingFunc = fromKeyframe.timingFunction; 1495 const T& fromValue = fromKeyframe.value; 1496 const T& toValue = toKeyframe.value; 1497 1498 // Now we have a source keyframe, origin keyframe and a timing function. 1499 // We can now process the progress and apply the frame. 1500 progress = (!progress || progress == 1 || it.key() == it2.key()) ? 1501 progress : applyTimingFunction(timingFunc, (progress - it.key()) / (it2.key() - it.key()), duration()); 1502 applyFrame(fromValue, toValue, progress); 1503#if QT_DEBUG_FPS 1504 ++m_fps.frames; 1505#endif 1506 } 1507 1508 QMap<qreal, KeyframeValueQt<T> > m_keyframeValues; 1509#if QT_DEBUG_FPS 1510 struct { 1511 QTime duration; 1512 int frames; 1513 } m_fps; 1514#endif 1515}; 1516 1517class TransformAnimationQt : public AnimationQt<TransformOperations> { 1518public: 1519 TransformAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) 1520 : AnimationQt<TransformOperations>(layer, values, boxSize, anim, name) 1521 { 1522 } 1523 1524 ~TransformAnimationQt() 1525 { 1526 if (m_fillsForwards) 1527 setCurrentTime(1); 1528 } 1529 1530 // The idea is that we let WebCore manage the transform operations and Qt just manage the 1531 // animation heartbeat and the bottom-line QTransform. We gain performance, not by using 1532 // Transform instead of TransformationMatrix, but by proper caching of items that are 1533 // expensive for WebCore to render. We want the rest to be as close to WebCore's idea as possible. 1534 virtual void applyFrame(const TransformOperations& sourceOperations, const TransformOperations& targetOperations, qreal progress) 1535 { 1536 TransformationMatrix transformMatrix; 1537 1538 bool validTransformLists = true; 1539 const int sourceOperationCount = sourceOperations.size(); 1540 if (sourceOperationCount) { 1541 if (targetOperations.size() != sourceOperationCount) 1542 validTransformLists = false; 1543 else { 1544 for (size_t j = 0; j < sourceOperationCount && validTransformLists; ++j) { 1545 if (!sourceOperations.operations()[j]->isSameType(*targetOperations.operations()[j])) 1546 validTransformLists = false; 1547 } 1548 } 1549 } 1550 1551 if (validTransformLists) { 1552 for (size_t i = 0; i < targetOperations.size(); ++i) 1553 targetOperations.operations()[i]->blend(sourceOperations.at(i), progress)->apply(transformMatrix, m_boxSize); 1554 } else { 1555 targetOperations.apply(m_boxSize, transformMatrix); 1556 transformMatrix.blend(m_sourceMatrix, progress); 1557 } 1558 1559 m_layer.data()->m_layer->setTransform(transformMatrix); 1560 // We force the actual opacity change, otherwise it would be ignored because of the animation. 1561 m_layer.data()->setBaseTransform(transformMatrix); 1562 } 1563 1564 virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) 1565 { 1566 AnimationQt<TransformOperations>::updateState(newState, oldState); 1567 if (!m_layer) 1568 return; 1569 1570 m_layer.data()->flushChanges(true); 1571 1572 // To increase FPS, we use a less accurate caching mechanism while animation is going on 1573 // this is a UX choice that should probably be customizable. 1574 if (newState == QAbstractAnimation::Running) { 1575 m_sourceMatrix = m_layer.data()->m_layer->transform(); 1576 m_layer.data()->m_transformAnimationRunning = true; 1577 } else if (newState == QAbstractAnimation::Stopped) { 1578 // We update the transform back to the default. This already takes fill-modes into account. 1579 m_layer.data()->m_transformAnimationRunning = false; 1580 if (m_layer && m_layer.data()->m_layer) 1581 m_layer.data()->setBaseTransform(m_layer.data()->m_layer->transform()); 1582 } 1583 } 1584 1585 TransformationMatrix m_sourceMatrix; 1586}; 1587 1588class OpacityAnimationQt : public AnimationQt<qreal> { 1589public: 1590 OpacityAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString& name) 1591 : AnimationQt<qreal>(layer, values, boxSize, anim, name) 1592 { 1593 } 1594 1595 ~OpacityAnimationQt() 1596 { 1597 if (m_fillsForwards) 1598 setCurrentTime(1); 1599 } 1600 1601 virtual void applyFrame(const qreal& fromValue, const qreal& toValue, qreal progress) 1602 { 1603 qreal opacity = qBound(qreal(0), fromValue + (toValue - fromValue) * progress, qreal(1)); 1604 1605 // FIXME: This is a hack, due to a probable QGraphicsScene bug. 1606 // Without this the opacity change doesn't always have immediate effect. 1607 if (m_layer.data()->scene() && !m_layer.data()->opacity() && opacity) 1608 m_layer.data()->scene()->update(); 1609 1610 m_layer.data()->m_layer->setOpacity(opacity); 1611 // We force the actual opacity change, otherwise it would be ignored because of the animation. 1612 m_layer.data()->setOpacity(opacity); 1613 } 1614 1615 virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) 1616 { 1617 AnimationQt<qreal>::updateState(newState, oldState); 1618 1619 if (m_layer) 1620 m_layer.data()->m_opacityAnimationRunning = (newState == QAbstractAnimation::Running); 1621 1622 // If stopped, we update the opacity back to the default. This already takes fill-modes into account. 1623 if (newState == Stopped) 1624 if (m_layer && m_layer.data()->m_layer) 1625 m_layer.data()->setOpacity(m_layer.data()->m_layer->opacity()); 1626 1627 } 1628}; 1629 1630bool GraphicsLayerQt::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) 1631{ 1632 if (!anim->duration() || !anim->iterationCount()) 1633 return false; 1634 1635 AnimationQtBase* newAnim = 0; 1636 1637 // Fixed: we might already have the Qt animation object associated with this WebCore::Animation object. 1638 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1639 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1640 if (*it) { 1641 AnimationQtBase* curAnimation = static_cast<AnimationQtBase*>(it->data()); 1642 if (curAnimation && curAnimation->m_webkitAnimation == anim) 1643 newAnim = curAnimation; 1644 } 1645 } 1646 1647 if (!newAnim) { 1648 switch (values.property()) { 1649 case AnimatedPropertyOpacity: 1650 newAnim = new OpacityAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName); 1651 break; 1652 case AnimatedPropertyWebkitTransform: 1653 newAnim = new TransformAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName); 1654 break; 1655 default: 1656 return false; 1657 } 1658 1659 // We make sure WebCore::Animation and QAnimation are on the same terms. 1660 newAnim->setLoopCount(anim->iterationCount()); 1661 newAnim->m_fillsForwards = anim->fillsForwards(); 1662 m_impl->m_animations.append(QWeakPointer<QAbstractAnimation>(newAnim)); 1663 QObject::connect(&m_impl->m_suspendTimer, SIGNAL(timeout()), newAnim, SLOT(resume())); 1664 } 1665 1666 // Flush now to avoid flicker. 1667 m_impl->flushChanges(false); 1668 1669 // Qhen fill-mode is backwards/both, we set the value to 0 before the delay takes place. 1670 if (anim->fillsBackwards()) 1671 newAnim->setCurrentTime(0); 1672 1673 newAnim->start(); 1674 1675 // We synchronize the animation's clock to WebCore's timeOffset. 1676 newAnim->setCurrentTime(timeOffset * 1000); 1677 1678 // We don't need to manage the animation object's lifecycle: 1679 // WebCore would call removeAnimations when it's time to delete. 1680 1681 return true; 1682} 1683 1684void GraphicsLayerQt::removeAnimationsForProperty(AnimatedPropertyID id) 1685{ 1686 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1687 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1688 if (!(*it)) 1689 continue; 1690 1691 AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data()); 1692 if (anim && anim->m_webkitPropertyID == id) { 1693 // We need to stop the animation right away, or it might flicker before it's deleted. 1694 anim->stop(); 1695 anim->deleteLater(); 1696 it = m_impl->m_animations.erase(it); 1697 --it; 1698 } 1699 } 1700} 1701 1702void GraphicsLayerQt::removeAnimationsForKeyframes(const String& name) 1703{ 1704 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1705 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1706 if (!(*it)) 1707 continue; 1708 1709 AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data()); 1710 if (anim && anim->m_keyframesName == QString(name)) { 1711 // We need to stop the animation right away, or it might flicker before it's deleted. 1712 anim->stop(); 1713 anim->deleteLater(); 1714 it = m_impl->m_animations.erase(it); 1715 --it; 1716 } 1717 } 1718} 1719 1720void GraphicsLayerQt::pauseAnimation(const String& name, double timeOffset) 1721{ 1722 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1723 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1724 if (!(*it)) 1725 continue; 1726 1727 AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data()); 1728 if (anim && anim->m_keyframesName == QString(name)) { 1729 // we synchronize the animation's clock to WebCore's timeOffset 1730 anim->setCurrentTime(timeOffset * 1000); 1731 anim->pause(); 1732 } 1733 } 1734} 1735 1736void GraphicsLayerQt::suspendAnimations(double time) 1737{ 1738 if (m_impl->m_suspendTimer.isActive()) { 1739 m_impl->m_suspendTimer.stop(); 1740 m_impl->m_suspendTimer.start(time * 1000); 1741 } else { 1742 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1743 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1744 if (QAbstractAnimation* anim = it->data()) 1745 anim->pause(); 1746 } 1747 } 1748} 1749 1750void GraphicsLayerQt::resumeAnimations() 1751{ 1752 if (m_impl->m_suspendTimer.isActive()) { 1753 m_impl->m_suspendTimer.stop(); 1754 QList<QWeakPointer<QAbstractAnimation> >::iterator it; 1755 for (it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { 1756 if (QAbstractAnimation* anim = it->data()) 1757 anim->resume(); 1758 } 1759 } 1760} 1761 1762#endif // QT_NO_ANIMATION 1763} 1764 1765#include <GraphicsLayerQt.moc> 1766 1767 1768#endif // QT_NO_GRAPHICSVIEW 1769