/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /*! \class QGraphicsSceneBspTreeIndex \brief The QGraphicsSceneBspTreeIndex class provides an implementation of a BSP indexing algorithm for discovering items in QGraphicsScene. \since 4.6 \ingroup graphicsview-api \internal QGraphicsSceneBspTreeIndex index use a BSP(Binary Space Partitioning) implementation to discover items quickly. This implementation is very efficient for static scenes. It has a depth that you can set. The depth directly affects performance and memory usage; the latter growing exponentially with the depth of the tree. With an optimal tree depth, the index can instantly determine the locality of items, even for scenes with thousands or millions of items. This also greatly improves rendering performance. By default, the depth value is 0, in which case Qt will guess a reasonable default depth based on the size, location and number of items in the scene. If these parameters change frequently, however, you may experience slowdowns as the index retunes the depth internally. You can avoid potential slowdowns by fixating the tree depth through setting this property. The depth of the tree and the size of the scene rectangle decide the granularity of the scene's partitioning. The size of each scene segment is determined by the following algorithm: The BSP tree has an optimal size when each segment contains between 0 and 10 items. \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex */ #include #ifndef QT_NO_GRAPHICSVIEW #include #include #include #include #include #include QT_BEGIN_NAMESPACE static inline int intmaxlog(int n) { return (n > 0 ? qMax(qCeil(qLn(qreal(n)) / qLn(qreal(2))), 5) : 0); } /*! Constructs a private scene bsp index. */ QGraphicsSceneBspTreeIndexPrivate::QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene) : QGraphicsSceneIndexPrivate(scene), bspTreeDepth(0), indexTimerId(0), restartIndexTimer(false), regenerateIndex(true), lastItemCount(0), purgePending(false), sortCacheEnabled(false), updatingSortCache(false) { } /*! This method will update the BSP index by removing the items from the temporary unindexed list and add them in the indexedItems list. This will also update the growingItemsBoundingRect if needed. This will update the BSP implementation as well. \internal */ void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() { Q_Q(QGraphicsSceneBspTreeIndex); if (!indexTimerId) return; q->killTimer(indexTimerId); indexTimerId = 0; purgeRemovedItems(); // Add unindexedItems to indexedItems for (int i = 0; i < unindexedItems.size(); ++i) { if (QGraphicsItem *item = unindexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); if (!freeItemIndexes.isEmpty()) { int freeIndex = freeItemIndexes.takeFirst(); item->d_func()->index = freeIndex; indexedItems[freeIndex] = item; } else { item->d_func()->index = indexedItems.size(); indexedItems << item; } } } // Determine whether we should regenerate the BSP tree. if (bspTreeDepth == 0) { int oldDepth = intmaxlog(lastItemCount); bspTreeDepth = intmaxlog(indexedItems.size()); static const int slack = 100; if (bsp.leafCount() == 0 || (oldDepth != bspTreeDepth && qAbs(lastItemCount - indexedItems.size()) > slack)) { // ### Crude algorithm. regenerateIndex = true; } } // Regenerate the tree. if (regenerateIndex) { regenerateIndex = false; bsp.initialize(sceneRect, bspTreeDepth); unindexedItems = indexedItems; lastItemCount = indexedItems.size(); } // Insert all unindexed items into the tree. for (int i = 0; i < unindexedItems.size(); ++i) { if (QGraphicsItem *item = unindexedItems.at(i)) { if (item->d_ptr->itemIsUntransformable()) { untransformableItems << item; continue; } if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) continue; bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect()); } } unindexedItems.clear(); } /*! \internal Removes stale pointers from all data structures. */ void QGraphicsSceneBspTreeIndexPrivate::purgeRemovedItems() { if (!purgePending && removedItems.isEmpty()) return; // Remove stale items from the BSP tree. bsp.removeItems(removedItems); // Purge this list. removedItems.clear(); freeItemIndexes.clear(); for (int i = 0; i < indexedItems.size(); ++i) { if (!indexedItems.at(i)) freeItemIndexes << i; } purgePending = false; } /*! \internal Starts or restarts the timer used for reindexing unindexed items. */ void QGraphicsSceneBspTreeIndexPrivate::startIndexTimer(int interval) { Q_Q(QGraphicsSceneBspTreeIndex); if (indexTimerId) { restartIndexTimer = true; } else { indexTimerId = q->startTimer(interval); } } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::resetIndex() { purgeRemovedItems(); for (int i = 0; i < indexedItems.size(); ++i) { if (QGraphicsItem *item = indexedItems.at(i)) { item->d_ptr->index = -1; Q_ASSERT(!item->d_ptr->itemDiscovered); unindexedItems << item; } } indexedItems.clear(); freeItemIndexes.clear(); untransformableItems.clear(); regenerateIndex = true; startIndexTimer(); } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::climbTree(QGraphicsItem *item, int *stackingOrder) { if (!item->d_ptr->children.isEmpty()) { QList childList = item->d_ptr->children; std::sort(childList.begin(), childList.end(), qt_closestLeaf); for (int i = 0; i < childList.size(); ++i) { QGraphicsItem *item = childList.at(i); if (!(item->flags() & QGraphicsItem::ItemStacksBehindParent)) climbTree(childList.at(i), stackingOrder); } item->d_ptr->globalStackingOrder = (*stackingOrder)++; for (int i = 0; i < childList.size(); ++i) { QGraphicsItem *item = childList.at(i); if (item->flags() & QGraphicsItem::ItemStacksBehindParent) climbTree(childList.at(i), stackingOrder); } } else { item->d_ptr->globalStackingOrder = (*stackingOrder)++; } } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::_q_updateSortCache() { Q_Q(QGraphicsSceneBspTreeIndex); _q_updateIndex(); if (!sortCacheEnabled || !updatingSortCache) return; updatingSortCache = false; int stackingOrder = 0; QList topLevels; const QList items = q->items(); for (int i = 0; i < items.size(); ++i) { QGraphicsItem *item = items.at(i); if (item && !item->d_ptr->parent) topLevels << item; } std::sort(topLevels.begin(), topLevels.end(), qt_closestLeaf); for (int i = 0; i < topLevels.size(); ++i) climbTree(topLevels.at(i), &stackingOrder); } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::invalidateSortCache() { Q_Q(QGraphicsSceneBspTreeIndex); if (!sortCacheEnabled || updatingSortCache) return; updatingSortCache = true; QMetaObject::invokeMethod(q, "_q_updateSortCache", Qt::QueuedConnection); } void QGraphicsSceneBspTreeIndexPrivate::addItem(QGraphicsItem *item, bool recursive) { if (!item) return; // Prevent reusing a recently deleted pointer: purge all removed item from our lists. purgeRemovedItems(); // Invalidate any sort caching; arrival of a new item means we need to resort. // Update the scene's sort cache settings. item->d_ptr->globalStackingOrder = -1; invalidateSortCache(); // Indexing requires sceneBoundingRect(), but because \a item might // not be completely constructed at this point, we need to store it in // a temporary list and schedule an indexing for later. if (item->d_ptr->index == -1) { Q_ASSERT(!unindexedItems.contains(item)); unindexedItems << item; startIndexTimer(0); } else { Q_ASSERT(indexedItems.contains(item)); qWarning("QGraphicsSceneBspTreeIndex::addItem: item has already been added to this BSP"); } if (recursive) { for (int i = 0; i < item->d_ptr->children.size(); ++i) addItem(item->d_ptr->children.at(i), recursive); } } void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool recursive, bool moveToUnindexedItems) { if (!item) return; if (item->d_ptr->index != -1) { Q_ASSERT(item->d_ptr->index < indexedItems.size()); Q_ASSERT(indexedItems.at(item->d_ptr->index) == item); Q_ASSERT(!item->d_ptr->itemDiscovered); freeItemIndexes << item->d_ptr->index; indexedItems[item->d_ptr->index] = 0; item->d_ptr->index = -1; if (item->d_ptr->itemIsUntransformable()) { untransformableItems.removeOne(item); } else if (item->d_ptr->inDestructor) { // Avoid virtual function calls from the destructor. purgePending = true; removedItems << item; } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect()); } } else { unindexedItems.removeOne(item); } invalidateSortCache(); // ### Only do this when removing from BSP? Q_ASSERT(item->d_ptr->index == -1); Q_ASSERT(!indexedItems.contains(item)); Q_ASSERT(!unindexedItems.contains(item)); Q_ASSERT(!untransformableItems.contains(item)); if (moveToUnindexedItems) addItem(item); if (recursive) { for (int i = 0; i < item->d_ptr->children.size(); ++i) removeItem(item->d_ptr->children.at(i), recursive, moveToUnindexedItems); } } QList QGraphicsSceneBspTreeIndexPrivate::estimateItems(const QRectF &rect, Qt::SortOrder order, bool onlyTopLevelItems) { Q_Q(QGraphicsSceneBspTreeIndex); if (onlyTopLevelItems && rect.isNull()) return q->QGraphicsSceneIndex::estimateTopLevelItems(rect, order); purgeRemovedItems(); _q_updateSortCache(); Q_ASSERT(unindexedItems.isEmpty()); QList rectItems = bsp.items(rect, onlyTopLevelItems); if (onlyTopLevelItems) { for (int i = 0; i < untransformableItems.size(); ++i) { QGraphicsItem *item = untransformableItems.at(i); if (!item->d_ptr->parent) { rectItems << item; } else { item = item->topLevelItem(); if (!rectItems.contains(item)) rectItems << item; } } } else { rectItems += untransformableItems; } sortItems(&rectItems, order, sortCacheEnabled, onlyTopLevelItems); return rectItems; } /*! Sort a list of \a itemList in a specific \a order and use the cache if requested. \internal */ void QGraphicsSceneBspTreeIndexPrivate::sortItems(QList *itemList, Qt::SortOrder order, bool sortCacheEnabled, bool onlyTopLevelItems) { if (order == Qt::SortOrder(-1)) return; if (onlyTopLevelItems) { if (order == Qt::DescendingOrder) std::sort(itemList->begin(), itemList->end(), qt_closestLeaf); else if (order == Qt::AscendingOrder) std::sort(itemList->begin(), itemList->end(), qt_notclosestLeaf); return; } if (sortCacheEnabled) { if (order == Qt::DescendingOrder) { std::sort(itemList->begin(), itemList->end(), closestItemFirst_withCache); } else if (order == Qt::AscendingOrder) { std::sort(itemList->begin(), itemList->end(), closestItemLast_withCache); } } else { if (order == Qt::DescendingOrder) { std::sort(itemList->begin(), itemList->end(), qt_closestItemFirst); } else if (order == Qt::AscendingOrder) { std::sort(itemList->begin(), itemList->end(), qt_closestItemLast); } } } /*! Constructs a BSP scene index for the given \a scene. */ QGraphicsSceneBspTreeIndex::QGraphicsSceneBspTreeIndex(QGraphicsScene *scene) : QGraphicsSceneIndex(*new QGraphicsSceneBspTreeIndexPrivate(scene), scene) { } QGraphicsSceneBspTreeIndex::~QGraphicsSceneBspTreeIndex() { Q_D(QGraphicsSceneBspTreeIndex); for (int i = 0; i < d->indexedItems.size(); ++i) { // Ensure item bits are reset properly. if (QGraphicsItem *item = d->indexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); item->d_ptr->index = -1; } } } /*! \internal Clear the all the BSP index. */ void QGraphicsSceneBspTreeIndex::clear() { Q_D(QGraphicsSceneBspTreeIndex); d->bsp.clear(); d->lastItemCount = 0; d->freeItemIndexes.clear(); for (int i = 0; i < d->indexedItems.size(); ++i) { // Ensure item bits are reset properly. if (QGraphicsItem *item = d->indexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); item->d_ptr->index = -1; } } d->indexedItems.clear(); d->unindexedItems.clear(); d->untransformableItems.clear(); d->regenerateIndex = true; } /*! Add the \a item into the BSP index. */ void QGraphicsSceneBspTreeIndex::addItem(QGraphicsItem *item) { Q_D(QGraphicsSceneBspTreeIndex); d->addItem(item); } /*! Remove the \a item from the BSP index. */ void QGraphicsSceneBspTreeIndex::removeItem(QGraphicsItem *item) { Q_D(QGraphicsSceneBspTreeIndex); d->removeItem(item); } /*! \internal Update the BSP when the \a item 's bounding rect has changed. */ void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *item) { if (!item) return; if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { return; // Item is not in BSP tree; nothing to do. } Q_D(QGraphicsSceneBspTreeIndex); QGraphicsItem *thatItem = const_cast(item); d->removeItem(thatItem, /*recursive=*/false, /*moveToUnindexedItems=*/true); for (int i = 0; i < item->d_ptr->children.size(); ++i) // ### Do we really need this? prepareBoundingRectChange(item->d_ptr->children.at(i)); } /*! Returns an estimation visible items that are either inside or intersect with the specified \a rect and return a list sorted using \a order. \a deviceTransform is the transformation apply to the view. */ QList QGraphicsSceneBspTreeIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); return const_cast(d)->estimateItems(rect, order); } QList QGraphicsSceneBspTreeIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); return const_cast(d)->estimateItems(rect, order, /*onlyTopLevels=*/true); } /*! \fn QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; Return all items in the BSP index and sort them using \a order. */ QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); const_cast(d)->purgeRemovedItems(); QList itemList; itemList.reserve(d->indexedItems.size() + d->unindexedItems.size()); // Rebuild the list of items to avoid holes. ### We could also just // compress the item lists at this point. QGraphicsItem *null = nullptr; // work-around for (at least) MSVC 2012 emitting // warning C4100 for its own header // when passing nullptr directly to remove_copy: std::remove_copy(d->indexedItems.cbegin(), d->indexedItems.cend(), std::back_inserter(itemList), null); std::remove_copy(d->unindexedItems.cbegin(), d->unindexedItems.cend(), std::back_inserter(itemList), null); d->sortItems(&itemList, order, d->sortCacheEnabled); return itemList; } /*! \property QGraphicsSceneBspTreeIndex::bspTreeDepth \brief the depth of the BSP index tree \since 4.6 This value determines the depth of BSP tree. The depth directly affects performance and memory usage; the latter growing exponentially with the depth of the tree. With an optimal tree depth, the index can instantly determine the locality of items, even for scenes with thousands or millions of items. This also greatly improves rendering performance. By default, the value is 0, in which case Qt will guess a reasonable default depth based on the size, location and number of items in the scene. If these parameters change frequently, however, you may experience slowdowns as the index retunes the depth internally. You can avoid potential slowdowns by fixating the tree depth through setting this property. The depth of the tree and the size of the scene rectangle decide the granularity of the scene's partitioning. The size of each scene segment is determined by the following algorithm: The BSP tree has an optimal size when each segment contains between 0 and 10 items. */ int QGraphicsSceneBspTreeIndex::bspTreeDepth() const { Q_D(const QGraphicsSceneBspTreeIndex); return d->bspTreeDepth; } void QGraphicsSceneBspTreeIndex::setBspTreeDepth(int depth) { Q_D(QGraphicsSceneBspTreeIndex); if (d->bspTreeDepth == depth) return; d->bspTreeDepth = depth; d->resetIndex(); } /*! \internal This method react to the \a rect change of the scene and reset the BSP tree index. */ void QGraphicsSceneBspTreeIndex::updateSceneRect(const QRectF &rect) { Q_D(QGraphicsSceneBspTreeIndex); d->sceneRect = rect; d->resetIndex(); } /*! \internal This method react to the \a change of the \a item and use the \a value to update the BSP tree if necessary. */ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) { Q_D(QGraphicsSceneBspTreeIndex); switch (change) { case QGraphicsItem::ItemFlagsChange: { // Handle ItemIgnoresTransformations QGraphicsItem::GraphicsItemFlags newFlags = *static_cast(value); bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations; 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(item); // Remove item and its descendants from the index and append // them to the list of unindexed items. Then, when the index // is updated, they will be put into the bsp-tree or the list // of untransformable items. d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); } break; } case QGraphicsItem::ItemZValueChange: d->invalidateSortCache(); break; case QGraphicsItem::ItemParentChange: { d->invalidateSortCache(); // Handle ItemIgnoresTransformations const QGraphicsItem *newParent = static_cast(value); 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 || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren; bool ancestorWillClipChildren = newParent && ((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(item); // Remove item and its descendants from the index and append // them to the list of unindexed items. Then, when the index // is updated, they will be put into the bsp-tree or the list // of untransformable items. d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); } break; } default: break; } } /*! \reimp Used to catch the timer event. \internal */ bool QGraphicsSceneBspTreeIndex::event(QEvent *event) { Q_D(QGraphicsSceneBspTreeIndex); if (event->type() == QEvent::Timer) { if (d->indexTimerId && static_cast(event)->timerId() == d->indexTimerId) { if (d->restartIndexTimer) { d->restartIndexTimer = false; } else { // this call will kill the timer d->_q_updateIndex(); } } } return QObject::event(event); } QT_END_NAMESPACE #include "moc_qgraphicsscenebsptreeindex_p.cpp" #endif // QT_NO_GRAPHICSVIEW