Unsolved qt optimize drawing of outlined text
-
I use such code to draw outlined text:
painter.setPen(Qt::black); painter.setBrush(Qt::white); QPainterPath ppath; ppath.addText({200., 200.}, font(), line); painter.drawPath(ppath);
it works, but so slow... For 100 captions it takes 30-50 milleseconds on 4GHz CPU, it is too long, because I want at least 10FPS, and I need much more stuff in addition to drawing of captions.
Any hint how speedup code bellow? I thought that Qt has font cache, but for some reason 50% of program spend inside libfreetype in
TT_RunIns
code, while I draw only digits, so I thought that it should cache all glyphs after several iterations. May be I need call some function to turn on font cache? Note for simplicity I draw text on the same positon, in real program of course I do it in the right way.#include <QApplication> #include <QElapsedTimer> #include <QPainter> #include <QTimer> #include <QWidget> #include <algorithm> struct MyW : public QWidget { void paintEvent(QPaintEvent *) override { QPainter painter{this}; painter.fillRect(rect(), Qt::red); painter.setPen(Qt::black); painter.setBrush(Qt::white); QVector<QString> lines; lines.resize(100); std::generate(std::begin(lines), std::end(lines), [] { return QString::number(qrand()); }); QElapsedTimer timer; timer.start(); for (const auto &line : lines) { QPainterPath ppath; ppath.addText({200., 200.}, font(), line); painter.drawPath(ppath); } qInfo("paint text takes %d ms", static_cast<int>(timer.elapsed())); } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); MyW w; w.resize(600, 800); w.show(); QTimer timer; QObject::connect(&timer, &QTimer::timeout, &app, [&w] { w.update(); }); timer.start(1000); return app.exec(); }
-
Hi,
You should profile your code however I think that the generation of your random list of text is one of the part that takes most of the time.
Do you really expect your application to generate that kind of data every time you paint.
-
You should profile your code
If you reread my post you see that I already did this.
but for some reason 50% of program spend inside libfreetype in TT_RunIns code
however I think that the generation of your random list of text is one of the part that takes most of the time.
Do you really expect your application to generate that kind of data every time you paint.You joking right? As you can see the time of generation was NOT counted,
QElapsedTimer
is started after all generation done. -
Hi
since you kindly provided ready to run code i played around with it a bit.
It seems that
ppath.addText({200., 200.}, font(), line);
is pretty heavy.
I tried with ppath.addRect instead and it much, much faster.
So im guessing on converting the font info to QPainterPath data is the heavy part and
if you google TT_RunIns, there is also mention of that being slow outside of Qt.So if you only draw numbers 0-9
you could pre create paths for them and use that ?just as a proof of concept. Warning. ugly code!
struct MyW : public QWidget { QPainterPath *cache[10] = {nullptr}; MyW(QWidget *par = nullptr) : QWidget(par) { for (int var = 0; var < 10; ++var) { cache[var] = new QPainterPath; // this leaks! cache[var]->addText({0,0}, font(), QString::number(var)); } } void paintEvent(QPaintEvent *) override { QPainter painter{this}; painter.fillRect(rect(), Qt::red); painter.setPen(Qt::black); painter.setBrush(Qt::white); QVector<QString> lines; lines.resize(100); std::generate(std::begin(lines), std::end(lines), [] { return QString::number(qrand()); }); QElapsedTimer timer; timer.start(); for (const auto &line : lines) { for (int ch = 0; ch < line.length(); ++ch) { const QPainterPath *path = cache[ line.at(ch).digitValue()]; painter.drawPath( path->translated(200+(ch*10),200) ); } } qInfo("paint text takes %d ms", static_cast<int>(timer.elapsed())); } };
note it leaks the paths! and the digit placement is off.
Just for speed test. this is on my pc
3 ms where the original code is 30-35.However, it might not be feasible for your real app if its not just numbers.
-
3 ms where the original code is 30-35.
I don't see so huge difference on my machine,
I enable permanently turbo-boost for one core, to make time measurements more fair,
and now original code takes 20ms (still too slow),
with your modification it takes 9ms.So 2x time speedup, great, but it is still 1/10 of frame (1 seconds / 10 / 10)
spend on just 100 captions, it looks like too slow for me.But if I emulate the glyph cache all works 0ms,
but for some reasonQPainterPath
don't use internal Qt's glyphs cache :(void paintEvent(QPaintEvent *) override { QPainter painter{this}; painter.fillRect(rect(), Qt::red); QVector<QString> lines; lines.resize(100); std::generate(std::begin(lines), std::end(lines), [] { return QString::number(qrand()); }); QFontMetrics fm{font()}; std::vector<QPixmap> ppaths; ppaths.reserve(lines.size()); for (const auto &line : lines) { const auto r = fm.boundingRect(line); QPixmap pix{r.size()}; QPainter pix_painter{&pix}; pix_painter.setPen(Qt::black); pix_painter.setBrush(Qt::white); QPainterPath ppath; ppath.addText({0., float(r.height()) / 2}, font(), line); pix_painter.drawPath(ppath); ppaths.push_back(pix); } QElapsedTimer timer; timer.start(); for (const auto &ppath : ppaths) { painter.drawPixmap(QPoint{200, 200}, ppath); } qInfo("paint text takes %d ms", static_cast<int>(timer.elapsed())); }
-
Hi
Hmm, yes. Drawing as pixmap really is much faster than drawPath (tried your new code also)
so i guess that will be the way to go.
However, if you plan on much more drawing,
QPainter might end up being to slow anyway to reach 10 FPS.What else are you planning to paint ?