Optimize QTextLayout/QTextEngine usage outside of QTextDocument.

When QTextLayout is used in a QTextDocument, many code paths use
special caches and thus greatly outperform the raw QTextLayout version
that operates directly on a QString.

This patch brings some of these optimizations also to the raw version.
We now also use a QFormatCollection in such cases and enable the
functionality of QTextEngine::indexAdditionalFormats() and
QTextEngine::resolveAdditionalFormats(). Thanks to that, we can greatly
speed up QTextEngine::format(), which now uses an amort O(1) hash table
lookup instead of a O(N) linear search.

The added benchmark shows a gain in the order of one magnitude:

./tst_bench_QText formattedLayout:long-many

before applying the patch:
378.19 msecs per iteration (total: 37,820, iterations: 100)
after applying the patch:
25.80 msecs per iteration (total: 2,580, iterations: 100)

Note: This change is source-incompatible for applications using the private
QTextEngine API.

Task-number: QTBUG-8389
Change-Id: Ifcf7a8902a394428979ea06a6d955f886ee739c7
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
This commit is contained in:
Milian Wolff 2012-09-03 18:06:17 +02:00 committed by Qt by Nokia
parent 3fe5715b9a
commit 0102f34f1e
3 changed files with 62 additions and 24 deletions

View File

@ -2198,11 +2198,11 @@ int QTextEngine::formatIndex(const QScriptItem *si) const
QTextCharFormat QTextEngine::format(const QScriptItem *si) const QTextCharFormat QTextEngine::format(const QScriptItem *si) const
{ {
QTextCharFormat format; QTextCharFormat format;
const QTextFormatCollection *formats = 0; const QTextFormatCollection *formats = this->formats();
if (block.docHandle()) {
formats = this->formats(); if (formats)
format = formats->charFormat(formatIndex(si)); format = formats->charFormat(formatIndex(si));
}
if (specialData && specialData->resolvedFormatIndices.isEmpty()) { if (specialData && specialData->resolvedFormatIndices.isEmpty()) {
int end = si->position + length(si); int end = si->position + length(si);
for (int i = 0; i < specialData->addFormats.size(); ++i) { for (int i = 0; i < specialData->addFormats.size(); ++i) {
@ -2289,11 +2289,15 @@ bool QTextEngine::atSpace(int position) const
void QTextEngine::indexAdditionalFormats() void QTextEngine::indexAdditionalFormats()
{ {
if (!block.docHandle())
return;
specialData->addFormatIndices.resize(specialData->addFormats.count()); specialData->addFormatIndices.resize(specialData->addFormats.count());
QTextFormatCollection * const formats = this->formats();
QTextFormatCollection *formats = this->formats();
if (!formats) {
Q_ASSERT(!block.docHandle());
specialData->formats.reset(new QTextFormatCollection);
formats = specialData->formats.data();
}
for (int i = 0; i < specialData->addFormats.count(); ++i) { for (int i = 0; i < specialData->addFormats.count(); ++i) {
specialData->addFormatIndices[i] = formats->indexForFormat(specialData->addFormats.at(i).format); specialData->addFormatIndices[i] = formats->indexForFormat(specialData->addFormats.at(i).format);
@ -2708,11 +2712,10 @@ public:
void QTextEngine::resolveAdditionalFormats() const void QTextEngine::resolveAdditionalFormats() const
{ {
if (!specialData || specialData->addFormats.isEmpty() if (!specialData || specialData->addFormats.isEmpty()
|| !block.docHandle()
|| !specialData->resolvedFormatIndices.isEmpty()) || !specialData->resolvedFormatIndices.isEmpty())
return; return;
QTextFormatCollection *collection = this->formats(); QTextFormatCollection *collection = formats();
specialData->resolvedFormatIndices.clear(); specialData->resolvedFormatIndices.clear();
QVector<int> indices(layoutData->items.count()); QVector<int> indices(layoutData->items.count());
@ -2748,16 +2751,17 @@ void QTextEngine::resolveAdditionalFormats() const
++endIt; ++endIt;
} }
QTextCharFormat format; QTextCharFormat format;
const QTextFormatCollection *formats = 0;
if (block.docHandle()) { if (block.docHandle()) {
formats = this->formats(); // when we have a docHandle, formatIndex might still return a valid index based
format = formats->charFormat(formatIndex(si)); // on the preeditPosition. for all other cases, we cleared the resolved format indices
format = collection->charFormat(formatIndex(si));
} }
foreach (int cur, currentFormats) { foreach (int cur, currentFormats) {
const QTextLayout::FormatRange &r = specialData->addFormats.at(cur); const QTextLayout::FormatRange &r = specialData->addFormats.at(cur);
Q_ASSERT (r.start <= si->position && r.start + r.length >= end); Q_ASSERT (r.start <= si->position && r.start + r.length >= end);
if (!specialData->addFormatIndices.isEmpty()) { if (!specialData->addFormatIndices.isEmpty()) {
format.merge(formats->format(specialData->addFormatIndices.at(cur))); format.merge(collection->format(specialData->addFormatIndices.at(cur)));
} else { } else {
format.merge(r.format); format.merge(r.format);
} }

View File

@ -541,7 +541,12 @@ public:
#ifdef QT_BUILD_COMPAT_LIB #ifdef QT_BUILD_COMPAT_LIB
return 0; // Compat should never reference this symbol return 0; // Compat should never reference this symbol
#else #else
if (block.docHandle())
return block.docHandle()->formatCollection(); return block.docHandle()->formatCollection();
else if (specialData)
return specialData->formats.data();
return 0;
#endif #endif
} }
QTextCharFormat format(const QScriptItem *si) const; QTextCharFormat format(const QScriptItem *si) const;
@ -619,6 +624,8 @@ public:
QList<QTextLayout::FormatRange> addFormats; QList<QTextLayout::FormatRange> addFormats;
QVector<int> addFormatIndices; QVector<int> addFormatIndices;
QVector<int> resolvedFormatIndices; QVector<int> resolvedFormatIndices;
// only used when no docHandle is available
QScopedPointer<QTextFormatCollection> formats;
}; };
SpecialData *specialData; SpecialData *specialData;

View File

@ -51,6 +51,7 @@
#include <qtest.h> #include <qtest.h>
Q_DECLARE_METATYPE(QTextDocument*) Q_DECLARE_METATYPE(QTextDocument*)
Q_DECLARE_METATYPE(QList<QTextLayout::FormatRange>)
class tst_QText: public QObject class tst_QText: public QObject
{ {
@ -80,6 +81,7 @@ private slots:
void layout_data(); void layout_data();
void layout(); void layout();
void formattedLayout_data();
void formattedLayout(); void formattedLayout();
void paintLayoutToPixmap(); void paintLayoutToPixmap();
void paintLayoutToPixmap_painterFill(); void paintLayoutToPixmap_painterFill();
@ -328,28 +330,53 @@ void tst_QText::layout()
} }
}*/ }*/
void tst_QText::formattedLayout() void tst_QText::formattedLayout_data()
{
//set up formatting
QList<QTextLayout::FormatRange> ranges;
{ {
QTest::addColumn<QString>("text");
QTest::addColumn<QList<QTextLayout::FormatRange> >("ranges");
QTextCharFormat format; QTextCharFormat format;
format.setForeground(QColor("steelblue")); format.setForeground(QColor("steelblue"));
{
QList<QTextLayout::FormatRange> ranges;
QTextLayout::FormatRange formatRange; QTextLayout::FormatRange formatRange;
formatRange.format = format; formatRange.format = format;
formatRange.start = 0; formatRange.start = 0;
formatRange.length = 50; formatRange.length = 50;
ranges.append(formatRange);
QTest::newRow("short-single") << m_shortLorem << ranges;
}
{
QList<QTextLayout::FormatRange> ranges;
QString text = m_lorem.repeated(100);
const int width = 1;
for (int i = 0; i < text.size(); i += width) {
QTextLayout::FormatRange formatRange;
formatRange.format.setForeground(QBrush(QColor(i % 255, 255, 255)));
formatRange.start = i;
formatRange.length = width;
ranges.append(formatRange); ranges.append(formatRange);
} }
QTextLayout layout(m_shortLorem); QTest::newRow("long-many") << m_shortLorem << ranges;
}
}
void tst_QText::formattedLayout()
{
QFETCH(QString, text);
QFETCH(QList<QTextLayout::FormatRange>, ranges);
QTextLayout layout(text);
layout.setAdditionalFormats(ranges); layout.setAdditionalFormats(ranges);
setupTextLayout(&layout); setupTextLayout(&layout);
QBENCHMARK { QBENCHMARK {
QTextLayout layout(m_shortLorem); QTextLayout layout(text);
layout.setAdditionalFormats(ranges); layout.setAdditionalFormats(ranges);
setupTextLayout(&layout); setupTextLayout(&layout);
} }