Add a new optimization flag to QGraphicsItem.

The new flag ItemContainsChildrenInShape is similar to the existing
flag ItemClipsChildrenToShape. Setting the new flag makes QGraphicsScene
assume that children are drawn within the shape of the current item
but this is not enforced by clipping. When an application manually
ensures this clipping boundary, setting the new flag removes the
overhead of enforcing the clip with ItemClipsChildrenToShape, while
still allowing other routines to behave more optimially by assuming
children are within the shape of the current item.

[ChangeLog][QtWidgets][QGraphicsItem] Added the
ItemContainsChildrenInShape flag that enables using optimizations
of ItemClipsChildrenToShape without the overhead of enforcing the clip.

Change-Id: I5496fe1ca331b77fd51e0df8a3ace2b8e939eaf2
Reviewed-by: Andreas Aardal Hanssen <andreas@hanssen.name>
This commit is contained in:
Dimitar Asenov 2014-03-12 14:11:45 -07:00 committed by The Qt Project
parent d13d03f012
commit 3270b4490b
7 changed files with 190 additions and 28 deletions

View File

@ -332,6 +332,8 @@
this flag is disabled; children can draw anywhere. This behavior is
enforced by QGraphicsView::drawItems() or
QGraphicsScene::drawItems(). This flag was introduced in Qt 4.3.
\note This flag is similar to ItemContainsChildrenInShape but in addition
enforces the containment by clipping the children.
\value ItemIgnoresTransformations The item ignores inherited
transformations (i.e., its position is still anchored to its parent, but
@ -423,6 +425,19 @@
ItemStopsClickFocusPropagation, but also suppresses focus-out. This flag
allows you to completely take over focus handling.
This flag was introduced in Qt 4.7. \endomit
\value ItemContainsChildrenInShape This flag indicates that all of the
item's direct or indirect children only draw within the item's shape.
Unlike ItemClipsChildrenToShape, this restriction is not enforced. Set
ItemContainsChildrenInShape when you manually assure that drawing
is bound to the item's shape and want to avoid the cost associated with
enforcing the clip. Setting this flag enables more efficient drawing and
collision detection. The flag is disabled by default.
\note If both this flag and ItemClipsChildrenToShape are set, the clip
will be enforced. This is equivalent to just setting
ItemClipsChildrenToShape.
.
This flag was introduced in Qt 5.4.
*/
/*!
@ -836,6 +851,10 @@ void QGraphicsItemPrivate::updateAncestorFlag(QGraphicsItem::GraphicsItemFlag ch
flag = AncestorIgnoresTransformations;
enabled = flags & QGraphicsItem::ItemIgnoresTransformations;
break;
case QGraphicsItem::ItemContainsChildrenInShape:
flag = AncestorContainsChildren;
enabled = flags & QGraphicsItem::ItemContainsChildrenInShape;
break;
default:
return;
}
@ -895,6 +914,8 @@ void QGraphicsItemPrivate::updateAncestorFlags()
flags |= AncestorClipsChildren;
if (pd->flags & QGraphicsItem::ItemIgnoresTransformations)
flags |= AncestorIgnoresTransformations;
if (pd->flags & QGraphicsItem::ItemContainsChildrenInShape)
flags |= AncestorContainsChildren;
}
if (ancestorFlags == flags)
@ -1831,6 +1852,11 @@ void QGraphicsItem::setFlags(GraphicsItemFlags flags)
d_ptr->markParentDirty(true);
}
if ((flags & ItemContainsChildrenInShape) != (oldFlags & ItemContainsChildrenInShape)) {
// Item children containtment changes. Propagate the ancestor flag to all children.
d_ptr->updateAncestorFlag(ItemContainsChildrenInShape);
}
if ((flags & ItemIgnoresTransformations) != (oldFlags & ItemIgnoresTransformations)) {
// Item children clipping changes. Propagate the ancestor flag to
// all children.
@ -2322,7 +2348,8 @@ void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly,
}
// Update children with explicitly = false.
const bool updateChildren = update && !((flags & QGraphicsItem::ItemClipsChildrenToShape)
const bool updateChildren = update && !((flags & QGraphicsItem::ItemClipsChildrenToShape
|| flags & QGraphicsItem::ItemContainsChildrenInShape)
&& !(flags & QGraphicsItem::ItemHasNoContents));
foreach (QGraphicsItem *child, children) {
if (!newVisible || !child->d_ptr->explicitlyHidden)
@ -2835,7 +2862,9 @@ QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectI
#ifndef QT_NO_GRAPHICSEFFECT
Q_Q(const QGraphicsItem);
QRectF brect = effectiveBoundingRect(q_ptr->boundingRect());
if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || topMostEffectItem == q)
if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren
|| topMostEffectItem == q)
return brect;
const QGraphicsItem *effectParent = parent;
@ -2847,6 +2876,7 @@ QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectI
brect = effectParent->mapRectToItem(q, effectRectInParentSpace);
}
if (effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren
|| topMostEffectItem == effectParent) {
return brect;
}
@ -7442,7 +7472,8 @@ QVariant QGraphicsItem::extension(const QVariant &variant) const
*/
void QGraphicsItem::addToIndex()
{
if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) {
if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) {
// ### add to child index only if applicable
return;
}
@ -7459,7 +7490,8 @@ void QGraphicsItem::addToIndex()
*/
void QGraphicsItem::removeFromIndex()
{
if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) {
if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) {
// ### remove from child index only if applicable
return;
}
@ -11451,6 +11483,9 @@ QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag)
case QGraphicsItem::ItemStopsFocusHandling:
str = "ItemStopsFocusHandling";
break;
case QGraphicsItem::ItemContainsChildrenInShape:
str = "ItemContainsChildrenInShape";
break;
}
debug << str;
return debug;

View File

@ -105,7 +105,8 @@ public:
ItemIsFocusScope = 0x8000, // internal
ItemSendsScenePositionChanges = 0x10000,
ItemStopsClickFocusPropagation = 0x20000,
ItemStopsFocusHandling = 0x40000
ItemStopsFocusHandling = 0x40000,
ItemContainsChildrenInShape = 0x80000
// NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag.
};
Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag)

View File

@ -172,7 +172,8 @@ public:
AncestorHandlesChildEvents = 0x1,
AncestorClipsChildren = 0x2,
AncestorIgnoresTransformations = 0x4,
AncestorFiltersChildEvents = 0x8
AncestorFiltersChildEvents = 0x8,
AncestorContainsChildren = 0x10
};
inline QGraphicsItemPrivate()
@ -213,7 +214,6 @@ public:
needSortChildren(0),
allChildrenDirty(0),
fullUpdatePending(0),
dirtyChildrenBoundingRect(1),
flags(0),
paintedViewBoundingRectsNeedRepaint(0),
dirtySceneTransform(1),
@ -239,6 +239,7 @@ public:
mayHaveChildWithGraphicsEffect(0),
isDeclarativeItem(0),
sendParentChangeNotification(0),
dirtyChildrenBoundingRect(1),
globalStackingOrder(-1),
q_ptr(0)
{
@ -544,7 +545,7 @@ public:
quint32 handlesChildEvents : 1;
quint32 itemDiscovered : 1;
quint32 hasCursor : 1;
quint32 ancestorFlags : 4;
quint32 ancestorFlags : 5;
quint32 cacheMode : 2;
quint32 hasBoundingRegionGranularity : 1;
quint32 isWidget : 1;
@ -555,10 +556,9 @@ public:
quint32 needSortChildren : 1;
quint32 allChildrenDirty : 1;
quint32 fullUpdatePending : 1;
quint32 dirtyChildrenBoundingRect : 1;
// Packed 32 bits
quint32 flags : 19;
quint32 flags : 20;
quint32 paintedViewBoundingRectsNeedRepaint : 1;
quint32 dirtySceneTransform : 1;
quint32 geometryChanged : 1;
@ -571,9 +571,9 @@ public:
quint32 filtersDescendantEvents : 1;
quint32 sceneTransformTranslateOnly : 1;
quint32 notifyBoundingRectChanged : 1;
quint32 notifyInvalidated : 1;
// New 32 bits
quint32 notifyInvalidated : 1;
quint32 mouseSetsFocus : 1;
quint32 explicitActivate : 1;
quint32 wantsActive : 1;
@ -585,7 +585,8 @@ public:
quint32 mayHaveChildWithGraphicsEffect : 1;
quint32 isDeclarativeItem : 1;
quint32 sendParentChangeNotification : 1;
quint32 padding : 21;
quint32 dirtyChildrenBoundingRect : 1;
quint32 padding : 19;
// Optional stacking order
int globalStackingOrder;

View File

@ -4717,7 +4717,8 @@ void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter *
wasDirtyParentSceneTransform = true;
}
const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape);
const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape
|| item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape);
bool drawItem = itemHasContents && !itemIsFullyTransparent;
if (drawItem || minimumRenderSize > 0.0) {
const QRectF brect = adjustedItemEffectiveBoundingRect(item);
@ -5221,7 +5222,8 @@ void QGraphicsScenePrivate::processDirtyItemsRecursive(QGraphicsItem *item, bool
// Process children.
if (itemHasChildren && item->d_ptr->dirtyChildren) {
const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape;
const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape
|| item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape;
// Items with no content are threated as 'dummy' items which means they are never drawn and
// 'processed', so the painted view bounding rect is never up-to-date. This means that whenever
// such an item changes geometry, its children have to take care of the update regardless

View File

@ -169,7 +169,8 @@ void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex()
untransformableItems << item;
continue;
}
if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)
if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)
continue;
bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect());
@ -351,7 +352,8 @@ void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool rec
// Avoid virtual function calls from the destructor.
purgePending = true;
removedItems << item;
} else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) {
} else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) {
bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect());
}
} else {
@ -510,7 +512,8 @@ void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *
return;
if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable()
|| (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) {
|| (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) {
return; // Item is not in BSP tree; nothing to do.
}
@ -641,8 +644,10 @@ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphics
QGraphicsItem::GraphicsItemFlags newFlags = *static_cast<const QGraphicsItem::GraphicsItemFlags *>(value);
bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations;
bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations;
bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape;
bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape;
bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape
|| item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape;
bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape
|| newFlags & QGraphicsItem::ItemContainsChildrenInShape;
if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) {
QGraphicsItem *thatItem = const_cast<QGraphicsItem *>(item);
// Remove item and its descendants from the index and append
@ -663,10 +668,13 @@ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphics
bool ignoredTransform = item->d_ptr->itemIsUntransformable();
bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations)
|| (newParent && newParent->d_ptr->itemIsUntransformable());
bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren;
bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren;
bool ancestorWillClipChildren = newParent
&& ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape)
|| (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren));
&& ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape
|| newParent->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape)
|| (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren
|| newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren));
if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) {
QGraphicsItem *thatItem = const_cast<QGraphicsItem *>(item);
// Remove item and its descendants from the index and append

View File

@ -278,7 +278,8 @@ void QGraphicsSceneIndexPrivate::recursive_items_helper(QGraphicsItem *item, QRe
Q_ASSERT(!item->d_ptr->dirtySceneTransform);
}
const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape);
const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape
|| item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape);
bool processItem = !itemIsFullyTransparent;
if (processItem) {
processItem = intersect(item, exposeRect, mode, viewTransform, intersectData);

View File

@ -377,6 +377,8 @@ private slots:
void itemClipsChildrenToShape5();
void itemClipsTextChildToShape();
void itemClippingDiscovery();
void itemContainsChildrenInShape();
void itemContainsChildrenInShape2();
void ancestorFlags();
void untransformable();
void contextMenuEventPropagation();
@ -5845,6 +5847,102 @@ void tst_QGraphicsItem::itemClippingDiscovery()
QCOMPARE(scene.itemAt(90, 90), (QGraphicsItem *)0);
}
class ItemCountsBoundingRectCalls : public QGraphicsRectItem
{
public:
ItemCountsBoundingRectCalls(const QRectF & rect, QGraphicsItem *parent = 0)
: QGraphicsRectItem(rect, parent), boundingRectCalls(0) {}
QRectF boundingRect () const {
++boundingRectCalls;
return QGraphicsRectItem::boundingRect();
}
mutable int boundingRectCalls;
};
void tst_QGraphicsItem::itemContainsChildrenInShape()
{
ItemCountsBoundingRectCalls *parent = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10));
ItemCountsBoundingRectCalls *childOutsideShape = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10), parent);
childOutsideShape->setPos(20,0);
QGraphicsScene scene;
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
scene.addItem(parent);
QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls);
int oldParentBoundingRectCalls = parent->boundingRectCalls;
int oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;
// First test that both items are searched if no optimization flags are set
QGraphicsItem* item = scene.itemAt(25,5);
QVERIFY(item == childOutsideShape);
QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls);
QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls);
oldParentBoundingRectCalls = parent->boundingRectCalls;
oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;
// Repeat the test to make sure that no caching/indexing is in effect
item = scene.itemAt(25,5);
QVERIFY(item == childOutsideShape);
QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls);
QVERIFY(parent->boundingRectCalls == childOutsideShape->boundingRectCalls);
oldParentBoundingRectCalls = parent->boundingRectCalls;
oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;
// Set the optimization flag and make sure that the child is not returned
// and that the child's boundingRect() method is never called.
parent->setFlag(QGraphicsItem::ItemContainsChildrenInShape);
item = scene.itemAt(25,5);
QVERIFY(!(item));
QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
QVERIFY(childOutsideShape->boundingRectCalls == oldChildBoundingRectCalls);
QVERIFY(parent->boundingRectCalls > childOutsideShape->boundingRectCalls);
}
void tst_QGraphicsItem::itemContainsChildrenInShape2()
{
//The tested flag behaves almost identically to ItemClipsChildrenToShape
//in terms of optimizations but does not enforce the clip.
//This test makes sure there is no clip.
QGraphicsScene scene;
QGraphicsItem *rect = scene.addRect(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::yellow));
QGraphicsItem *ellipse = scene.addEllipse(0, 0, 100, 100, QPen(Qt::NoPen), QBrush(Qt::green));
ellipse->setParentItem(rect);
QGraphicsItem *clippedEllipse = scene.addEllipse(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::blue));
clippedEllipse->setParentItem(ellipse);
QGraphicsItem *clippedEllipse2 = scene.addEllipse(0, 0, 25, 25, QPen(Qt::NoPen), QBrush(Qt::red));
clippedEllipse2->setParentItem(clippedEllipse);
QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));
QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape));
ellipse->setFlags(QGraphicsItem::ItemContainsChildrenInShape);
QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));
QVERIFY((ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape));
QImage image(100, 100, QImage::Format_ARGB32_Premultiplied);
image.fill(0);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
scene.render(&painter);
painter.end();
QCOMPARE(image.pixel(2, 2), QColor(Qt::yellow).rgba());
QCOMPARE(image.pixel(12, 12), QColor(Qt::red).rgba());
QCOMPARE(image.pixel(2, 25), QColor(Qt::blue).rgba());
QCOMPARE(image.pixel(2, 50), QColor(Qt::green).rgba());
}
void tst_QGraphicsItem::ancestorFlags()
{
QGraphicsItem *level1 = new QGraphicsRectItem;
@ -5965,11 +6063,27 @@ void tst_QGraphicsItem::ancestorFlags()
// Nobody handles child events
level21->setHandlesChildEvents(false);
for (int i = 0; i < 2; ++i) {
QGraphicsItem::GraphicsItemFlag flag = !i ? QGraphicsItem::ItemClipsChildrenToShape
: QGraphicsItem::ItemIgnoresTransformations;
int ancestorFlag = !i ? QGraphicsItemPrivate::AncestorClipsChildren
: QGraphicsItemPrivate::AncestorIgnoresTransformations;
for (int i = 0; i < 3; ++i) {
QGraphicsItem::GraphicsItemFlag flag;
int ancestorFlag;
switch (i) {
case(0):
flag = QGraphicsItem::ItemClipsChildrenToShape;
ancestorFlag = QGraphicsItemPrivate::AncestorClipsChildren;
break;
case(1):
flag = QGraphicsItem::ItemIgnoresTransformations;
ancestorFlag = QGraphicsItemPrivate::AncestorIgnoresTransformations;
break;
case(2):
flag = QGraphicsItem::ItemContainsChildrenInShape;
ancestorFlag = QGraphicsItemPrivate::AncestorContainsChildren;
break;
default:
qFatal("Unknown ancestor flag, please fix!");
break;
}
QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);