QQuickText: Don't store node engine per node.
authorRobin Burchell <robin.burchell@viroteck.net>
Mon, 1 Jun 2015 16:03:04 +0000 (19:03 +0300)
committerRobin Burchell <robin.burchell@viroteck.net>
Sat, 6 Jun 2015 11:33:31 +0000 (11:33 +0000)
Since these are only used during one particular phase of dealing with text, it
is extremely wasteful to heap allocate them and keep them around for the entire
lifetime of the node (~3kb of total allocation _each_ according to OS X).

Removing these cuts around 100mb of transient allocations off the qmlbench text
creation benchmark (and takes the total allocations during a test run from
~496 MB to ~389 MB). It also improves the approximate throughput for creation
of text items by ~5%.

Change-Id: I45c8a50879ed545da1fb13ab3c2c5d857b112cf7
Reviewed-by: Gunnar Sletta <gunnar@sletta.org>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
src/quick/items/qquicktextedit.cpp
src/quick/items/qquicktextedit_p_p.h
src/quick/items/qquicktextnode.cpp
src/quick/items/qquicktextnode_p.h

index 0601932..7c00da7 100644 (file)
@@ -1849,6 +1849,14 @@ void QQuickTextEdit::invalidateFontCaches()
     }
 }
 
+inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor)
+{
+    *engine = QQuickTextNodeEngine();
+    engine->setTextColor(textColor);
+    engine->setSelectedTextColor(selectedTextColor);
+    engine->setSelectionColor(selectionColor);
+}
+
 QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
 {
     Q_UNUSED(updatePaintNodeData);
@@ -1874,6 +1882,8 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
     while (nodeIterator != d->textNodeMap.end() && !(*nodeIterator)->dirty())
         ++nodeIterator;
 
+    QQuickTextNodeEngine engine;
+    QQuickTextNodeEngine frameDecorationsEngine;
 
     if (!oldNode || nodeIterator < d->textNodeMap.end()) {
 
@@ -1893,6 +1903,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
 
         // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
         rootNode->resetFrameDecorations(d->createTextNode());
+        resetEngine(&frameDecorationsEngine, d->color, d->selectedTextColor, d->selectionColor);
 
         QQuickTextNode *node = 0;
 
@@ -1912,11 +1923,12 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
         while (!frames.isEmpty()) {
             QTextFrame *textFrame = frames.takeFirst();
             frames.append(textFrame->childFrames());
-            rootNode->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame);
+            frameDecorationsEngine.addFrameDecorations(d->document, textFrame);
 
             if (textFrame->lastPosition() < firstDirtyPos || (firstCleanNode && textFrame->firstPosition() >= firstCleanNode->startPos()))
                 continue;
             node = d->createTextNode();
+            resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor);
 
             if (textFrame->firstPosition() > textFrame->lastPosition()
                     && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
@@ -1925,8 +1937,8 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
                 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout());
                 QTextCharFormat format = a->formatAccessor(pos);
                 QTextBlock block = textFrame->firstCursorPosition().block();
-                node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
-                node->m_engine->addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
+                engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+                engine.addTextObject(QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
                                               pos, textFrame->frameFormat().position());
                 nodeStart = pos;
             } else if (qobject_cast<QTextTable*>(textFrame)) { // To keep things simple, map text tables as one text node
@@ -1934,7 +1946,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
                 nodeOffset =  d->document->documentLayout()->frameBoundingRect(textFrame).topLeft();
                 updateNodeTransform(node, nodeOffset);
                 while (!it.atEnd())
-                    node->m_engine->addTextBlock(d->document, (it++).currentBlock(), -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
+                    engine.addTextBlock(d->document, (it++).currentBlock(), -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
                 nodeStart = textFrame->firstPosition();
             } else {
                 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
@@ -1951,13 +1963,13 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
                     if (block.position() < firstDirtyPos)
                         continue;
 
-                    if (!node->m_engine->hasContents()) {
+                    if (!engine.hasContents()) {
                         nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
                         updateNodeTransform(node, nodeOffset);
                         nodeStart = block.position();
                     }
 
-                    node->m_engine->addTextBlock(d->document, block, -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
+                    engine.addTextBlock(d->document, block, -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
                     currentNodeSize += block.length();
 
                     if ((it.atEnd()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the frame
@@ -1966,15 +1978,16 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
                     QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position());
                     if (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart) {
                         currentNodeSize = 0;
-                        d->addCurrentTextNodeToRoot(rootNode, node, nodeIterator, nodeStart);
+                        d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
                         node = d->createTextNode();
+                        resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor);
                         nodeStart = block.next().position();
                     }
                 }
             }
-            d->addCurrentTextNodeToRoot(rootNode, node, nodeIterator, nodeStart);
+            d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
         }
-        rootNode->frameDecorationsNode->m_engine->addToSceneGraph(rootNode->frameDecorationsNode, QQuickText::Normal, QColor());
+        frameDecorationsEngine.addToSceneGraph(rootNode->frameDecorationsNode, QQuickText::Normal, QColor());
         // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
         rootNode->prependChildNode(rootNode->frameDecorationsNode);
 
@@ -2489,9 +2502,9 @@ void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
     }
 }
 
-void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
+void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
 {
-    node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
+    engine->addToSceneGraph(node, QQuickText::Normal, QColor());
     it = textNodeMap.insert(it, new TextNode(startPos, node));
     ++it;
     root->appendChildNode(node);
@@ -2502,7 +2515,6 @@ QQuickTextNode *QQuickTextEditPrivate::createTextNode()
     Q_Q(QQuickTextEdit);
     QQuickTextNode* node = new QQuickTextNode(q);
     node->setUseNativeRenderer(renderType == QQuickTextEdit::NativeRendering);
-    node->initEngine(color, selectedTextColor, selectionColor);
     return node;
 }
 
index 20c0d1c..a6e6fae 100644 (file)
@@ -57,6 +57,7 @@ class QTextLayout;
 class QQuickTextDocumentWithImageResources;
 class QQuickTextControl;
 class QQuickTextNode;
+class QQuickTextNodeEngine;
 
 class QQuickTextEditPrivate : public QQuickImplicitSizeItemPrivate
 {
@@ -126,7 +127,7 @@ public:
 
     void setNativeCursorEnabled(bool) {}
     void handleFocusEvent(QFocusEvent *event);
-    void addCurrentTextNodeToRoot(QSGTransformNode *, QQuickTextNode*, TextNodeIterator&, int startPos);
+    void addCurrentTextNodeToRoot(QQuickTextNodeEngine *, QSGTransformNode *, QQuickTextNode*, TextNodeIterator&, int startPos);
     QQuickTextNode* createTextNode();
 
 #ifndef QT_NO_IM
index 010a443..d40dedd 100644 (file)
@@ -186,17 +186,6 @@ void QQuickTextNode::clearCursor()
     m_cursorNode = 0;
 }
 
-void QQuickTextNode::initEngine(const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor, const QColor& anchorColor, const QPointF &position)
-{
-    m_engine.reset(new QQuickTextNodeEngine);
-    m_engine->m_hasContents = false;
-    m_engine->setTextColor(textColor);
-    m_engine->setSelectedTextColor(selectedTextColor);
-    m_engine->setSelectionColor(selectionColor);
-    m_engine->setAnchorColor(anchorColor);
-    m_engine->setPosition(position);
-}
-
 void QQuickTextNode::addRectangleNode(const QRectF &rect, const QColor &color)
 {
     QSGRenderContext *sg = QQuickItemPrivate::get(m_ownerElement)->sceneGraphRenderContext();
@@ -224,7 +213,12 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
                                   const QColor &selectionColor, const QColor &selectedTextColor,
                                   int selectionStart, int selectionEnd)
 {
-    initEngine(textColor, selectedTextColor, selectionColor, anchorColor);
+    QQuickTextNodeEngine engine;
+    engine.setTextColor(textColor);
+    engine.setSelectedTextColor(selectedTextColor);
+    engine.setSelectionColor(selectionColor);
+    engine.setAnchorColor(anchorColor);
+    engine.setPosition(position);
 
     QList<QTextFrame *> frames;
     frames.append(textDocument->rootFrame());
@@ -232,7 +226,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
         QTextFrame *textFrame = frames.takeFirst();
         frames.append(textFrame->childFrames());
 
-        m_engine->addFrameDecorations(textDocument, textFrame);
+        engine.addFrameDecorations(textDocument, textFrame);
 
         if (textFrame->firstPosition() > textFrame->lastPosition()
          && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
@@ -242,23 +236,23 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
             QRectF rect = a->frameBoundingRect(textFrame);
 
             QTextBlock block = textFrame->firstCursorPosition().block();
-            m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
-            m_engine->addTextObject(rect.topLeft(), format, QQuickTextNodeEngine::Unselected, textDocument,
+            engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+            engine.addTextObject(rect.topLeft(), format, QQuickTextNodeEngine::Unselected, textDocument,
                                  pos, textFrame->frameFormat().position());
         } else {
             QTextFrame::iterator it = textFrame->begin();
 
             while (!it.atEnd()) {
-                Q_ASSERT(!m_engine->currentLine().isValid());
+                Q_ASSERT(!engine.currentLine().isValid());
 
                 QTextBlock block = it.currentBlock();
-                m_engine->addTextBlock(textDocument, block, position, textColor, anchorColor, selectionStart, selectionEnd);
+                engine.addTextBlock(textDocument, block, position, textColor, anchorColor, selectionStart, selectionEnd);
                 ++it;
             }
         }
     }
 
-    m_engine->addToSceneGraph(this, style, styleColor);
+    engine.addToSceneGraph(this, style, styleColor);
 }
 
 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
@@ -268,7 +262,12 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
                                 int selectionStart, int selectionEnd,
                                 int lineStart, int lineCount)
 {
-    initEngine(color, selectedTextColor, selectionColor, anchorColor, position);
+    QQuickTextNodeEngine engine;
+    engine.setTextColor(color);
+    engine.setSelectedTextColor(selectedTextColor);
+    engine.setSelectionColor(selectionColor);
+    engine.setAnchorColor(anchorColor);
+    engine.setPosition(position);
 
 #ifndef QT_NO_IM
     int preeditLength = textLayout->preeditAreaText().length();
@@ -276,7 +275,7 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
 #endif
 
     QVarLengthArray<QTextLayout::FormatRange> colorChanges;
-    m_engine->mergeFormats(textLayout, &colorChanges);
+    engine.mergeFormats(textLayout, &colorChanges);
 
     lineCount = lineCount >= 0
             ? qMin(lineStart + lineCount, textLayout->lineCount())
@@ -297,11 +296,11 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
         }
 #endif
 
-        m_engine->setCurrentLine(line);
-        m_engine->addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
+        engine.setCurrentLine(line);
+        engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
     }
 
-    m_engine->addToSceneGraph(this, style, styleColor);
+    engine.addToSceneGraph(this, style, styleColor);
 }
 
 void QQuickTextNode::deleteContent()
index c7b9804..31cc23b 100644 (file)
@@ -101,14 +101,10 @@ public:
     void setUseNativeRenderer(bool on) { m_useNativeRenderer = on; }
 
 private:
-    void initEngine(const QColor &textColor, const QColor &selectedTextColor, const QColor &selectionColor, const QColor& anchorColor = QColor()
-            , const QPointF &position = QPointF());
-
     QSGRectangleNode *m_cursorNode;
     QList<QSGTexture *> m_textures;
     QQuickItem *m_ownerElement;
     bool m_useNativeRenderer;
-    QScopedPointer<QQuickTextNodeEngine> m_engine;
 
     friend class QQuickTextEdit;
     friend class QQuickTextEditPrivate;