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:
parent
3fe5715b9a
commit
0102f34f1e
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user