Add a new optimization property to QGraphicsScene

The minimumRenderSize is a qreal value that is used as a lower bound to
determine what items are visible when a scene is rendered. If an item's
view-transformed width or height are less than minimumRenderSize then
this item is considered to insignificantly affect the final result and
is not drawn. If the item clips its children to its shape they are
automatically not drawn. This greatly reduces the drawing overhead
for scenes with many items rendered in a zoomed out view.

[ChangeLog][QtWidgets][QGraphicsScene] Added the minimumRenderSize
property which can be used to speed up rendering by not painting
items, smaller than a give size.

Change-Id: Ie208234707dffb4d2fc620fc5d1514e0c144d9a8
Reviewed-by: Andreas Aardal Hanssen <andreas@hanssen.name>
bb10
Dimitar Asenov 2014-03-12 14:19:04 -07:00 committed by The Qt Project
parent 4d09a54b75
commit 9891c5051f
5 changed files with 154 additions and 9 deletions

View File

@ -299,6 +299,7 @@ QGraphicsScenePrivate::QGraphicsScenePrivate()
painterStateProtection(true),
sortCacheEnabled(false),
allItemsIgnoreTouchEvents(true),
minimumRenderSize(0.0),
selectionChanging(0),
rectAdjust(2),
focusItem(0),
@ -4718,17 +4719,37 @@ void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter *
const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape);
bool drawItem = itemHasContents && !itemIsFullyTransparent;
if (drawItem) {
if (drawItem || minimumRenderSize > 0.0) {
const QRectF brect = adjustedItemEffectiveBoundingRect(item);
ENSURE_TRANSFORM_PTR
QRect viewBoundingRect = translateOnlyTransform ? brect.translated(transformPtr->dx(), transformPtr->dy()).toAlignedRect()
: transformPtr->mapRect(brect).toAlignedRect();
viewBoundingRect.adjust(-int(rectAdjust), -int(rectAdjust), rectAdjust, rectAdjust);
if (widget)
item->d_ptr->paintedViewBoundingRects.insert(widget, viewBoundingRect);
drawItem = exposedRegion ? exposedRegion->intersects(viewBoundingRect)
: !viewBoundingRect.normalized().isEmpty();
if (!drawItem) {
QRectF preciseViewBoundingRect = translateOnlyTransform ? brect.translated(transformPtr->dx(), transformPtr->dy())
: transformPtr->mapRect(brect);
bool itemIsTooSmallToRender = false;
if (minimumRenderSize > 0.0
&& (preciseViewBoundingRect.width() < minimumRenderSize
|| preciseViewBoundingRect.height() < minimumRenderSize)) {
itemIsTooSmallToRender = true;
drawItem = false;
}
bool itemIsOutsideVisibleRect = false;
if (drawItem) {
QRect viewBoundingRect = preciseViewBoundingRect.toAlignedRect();
viewBoundingRect.adjust(-int(rectAdjust), -int(rectAdjust), rectAdjust, rectAdjust);
if (widget)
item->d_ptr->paintedViewBoundingRects.insert(widget, viewBoundingRect);
drawItem = exposedRegion ? exposedRegion->intersects(viewBoundingRect)
: !viewBoundingRect.normalized().isEmpty();
itemIsOutsideVisibleRect = !drawItem;
}
if (itemIsTooSmallToRender || itemIsOutsideVisibleRect) {
// We cannot simply use !drawItem here. If we did it is possible
// to enter the outter if statement with drawItem == false and minimumRenderSize > 0
// and finally end up inside this inner if, even though none of the above two
// conditions are met. In that case we should not return from this function
// but call draw() instead.
if (!itemHasChildren)
return;
if (itemClipsChildrenToShape) {
@ -5729,6 +5750,49 @@ bool QGraphicsScene::sendEvent(QGraphicsItem *item, QEvent *event)
return d->sendEvent(item, event);
}
/*!
\property QGraphicsScene::minimumRenderSize
\since 5.4
\brief the minimal view-transformed size an item must have to be drawn
When the scene is rendered, any item whose width or height, transformed
to the target view, is smaller that minimumRenderSize(), will not be
rendered. If an item is not rendered and it clips its children items
they will also not be rendered. Set this value to speed up rendering
of scenes with many objects rendered on a zoomed out view.
The default value is 0. If unset, or if set to 0 or a negative value,
all items will always be rendered.
For example, setting this property can be especially useful if a scene
is rendered by multiple views, one of which serves as an overview which
always displays all items. In scenes with many items, such a view will
use a high scaling factor so that all items can be shown. Due to the
scaling, smaller items will only make an insignificant contribution to
the final rendered scene. To avoid drawing these items and reduce the
time necessary to render the scene, you can call setMinimumRenderSize()
with a non-negative value.
\note Items that are not drawn as a result of being too small, are still
returned by methods such as items() and itemAt(), and participate in
collision detection and interactions. It is recommended that you set
minimumRenderSize() to a value less than or equal to 1 in order to
avoid large unrendered items that are interactive.
\sa QStyleOptionGraphicsItem::levelOfDetailFromTransform()
*/
qreal QGraphicsScene::minimumRenderSize() const
{
Q_D(const QGraphicsScene);
return d->minimumRenderSize;
}
void QGraphicsScene::setMinimumRenderSize(qreal minSize)
{
Q_D(QGraphicsScene);
d->minimumRenderSize = minSize;
update();
}
void QGraphicsScenePrivate::addView(QGraphicsView *view)
{
views << view;

View File

@ -107,6 +107,7 @@ class Q_WIDGETS_EXPORT QGraphicsScene : public QObject
Q_PROPERTY(QFont font READ font WRITE setFont)
Q_PROPERTY(bool sortCacheEnabled READ isSortCacheEnabled WRITE setSortCacheEnabled)
Q_PROPERTY(bool stickyFocus READ stickyFocus WRITE setStickyFocus)
Q_PROPERTY(qreal minimumRenderSize READ minimumRenderSize WRITE setMinimumRenderSize)
public:
enum ItemIndexMethod {
@ -249,6 +250,9 @@ public:
bool sendEvent(QGraphicsItem *item, QEvent *event);
qreal minimumRenderSize() const;
void setMinimumRenderSize(qreal minSize);
public Q_SLOTS:
void update(const QRectF &rect = QRectF());
void invalidate(const QRectF &rect = QRectF(), SceneLayers layers = AllLayers);

View File

@ -117,6 +117,8 @@ public:
quint32 allItemsIgnoreTouchEvents : 1;
quint32 padding : 15;
qreal minimumRenderSize;
QRectF growingItemsBoundingRect;
void _q_emitUpdated();

View File

@ -3702,6 +3702,8 @@ QStyleOptionGraphicsItem::QStyleOptionGraphicsItem(int version)
of the painter used to draw the item. By default, if no
transformations are applied, its value is 1. If zoomed out 1:2, the level
of detail will be 0.5, and if zoomed in 2:1, its value is 2.
\sa QGraphicsScene::minimumRenderSize()
*/
qreal QStyleOptionGraphicsItem::levelOfDetailFromTransform(const QTransform &worldTransform)
{

View File

@ -267,6 +267,7 @@ private slots:
void removeFullyTransparentItem();
void zeroScale();
void focusItemChangedSignal();
void minimumRenderSize();
// task specific tests below me
void task139710_bspTreeCrash();
@ -4678,6 +4679,78 @@ void tst_QGraphicsScene::focusItemChangedSignal()
}
class ItemCountsPaintCalls : public QGraphicsRectItem
{
public:
ItemCountsPaintCalls(const QRectF & rect, QGraphicsItem *parent = 0)
: QGraphicsRectItem(rect, parent), repaints(0) {}
void paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 )
{
QGraphicsRectItem::paint(painter, option, widget);
++repaints;
}
int repaints;
};
void tst_QGraphicsScene::minimumRenderSize()
{
Q_CHECK_PAINTEVENTS
ItemCountsPaintCalls *bigParent = new ItemCountsPaintCalls(QRectF(0,0,100,100));
ItemCountsPaintCalls *smallChild = new ItemCountsPaintCalls(QRectF(0,0,10,10), bigParent);
ItemCountsPaintCalls *smallerGrandChild = new ItemCountsPaintCalls(QRectF(0,0,1,1), smallChild);
QGraphicsScene scene;
scene.addItem(bigParent);
CustomView view;
view.setScene(&scene);
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
qApp->processEvents();
// Initially, everything should be repainted the same number of times
int viewRepaints = 0;
QTRY_VERIFY(view.repaints > viewRepaints);
viewRepaints = view.repaints;
QVERIFY(viewRepaints == bigParent->repaints);
QVERIFY(viewRepaints == smallChild->repaints);
QVERIFY(viewRepaints == smallerGrandChild->repaints);
// Setting a minimum render size should cause a repaint
scene.setMinimumRenderSize(0.5);
qApp->processEvents();
QTRY_VERIFY(view.repaints > viewRepaints);
viewRepaints = view.repaints;
QVERIFY(viewRepaints == bigParent->repaints);
QVERIFY(viewRepaints == smallChild->repaints);
QVERIFY(viewRepaints == smallerGrandChild->repaints);
// Scaling should cause a repaint of big items only.
view.scale(0.1, 0.1);
qApp->processEvents();
QTRY_VERIFY(view.repaints > viewRepaints);
viewRepaints = view.repaints;
QVERIFY(viewRepaints == bigParent->repaints);
QVERIFY(viewRepaints == smallChild->repaints);
QVERIFY(smallChild->repaints > smallerGrandChild->repaints);
// Scaling further should cause even fewer items to be repainted
view.scale(0.1, 0.1); // Stacks with previous scale
qApp->processEvents();
QTRY_VERIFY(view.repaints > viewRepaints);
viewRepaints = view.repaints;
QVERIFY(viewRepaints == bigParent->repaints);
QVERIFY(bigParent->repaints > smallChild->repaints);
QVERIFY(smallChild->repaints > smallerGrandChild->repaints);
}
void tst_QGraphicsScene::taskQTBUG_15977_renderWithDeviceCoordinateCache()
{
QGraphicsScene scene;