-
Bug
-
Resolution: Incomplete
-
Not Evaluated
-
None
-
6.10.0
I found that OBS Studio on Linux was burning close to a full CPU core repainting the volume meter every 16 ms. I've documented what went wrong at https://github.com/obsproject/obs-studio/issues/12758#issuecomment-3475952776. At this point I believe the issue is caused by Qt bugs.
Profiler screenshot attached.
Example stack trace:
#5 (anonymous namespace)::QResourceRoot::findNode (this=this@entry=0x555555adadd0, _path=..., locale=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/io/qresource.cpp:858 #6 0x00007ffff2d422b9 in QResourcePrivate::load (this=this@entry=0x555557dfdea0, file=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/io/qresource.cpp:339 #7 0x00007ffff2d42b1b in QResourcePrivate::ensureInitialized (this=this@entry=0x555557dfdea0) at /usr/src/debug/qt6-base/qtbase/src/corelib/io/qresource.cpp:389 #8 0x00007ffff2d42f57 in QResource::fileName (this=0x555557dfd0d8) at /usr/src/debug/qt6-base/qtbase/src/corelib/io/qresource.cpp:574 #9 QResourceFileEngine::open (this=<optimized out>, flags=..., permissions=std::optional [no contained value]) at /usr/src/debug/qt6-base/qtbase/src/corelib/io/qresource.cpp:1441 #10 0x00007ffff2d2121f in QFile::open (this=0x555557b7e000, mode=...) at /usr/src/debug/qt6-base/qtbase/src/corelib/global/qflags.h:77 #11 0x00007ffff355b3c2 in QImageReaderPrivate::initHandler (this=0x555557df9570) at /usr/src/debug/qt6-base/qtbase/src/gui/image/qimagereader.cpp:513 #12 0x00007ffff355b6f9 in QImageReader::supportsOption (this=0x7fffffffae00, option=QImageIOHandler::ScaledSize) at /usr/src/debug/qt6-base/qtbase/src/gui/image/qimagereader.cpp:1410 #13 0x00007ffff35145a2 in (anonymous namespace)::ImageReader::ImageReader (this=0x7fffffffae00, fileName=..., size=...) at /usr/src/debug/qt6-base/qtbase/src/gui/image/qicon.cpp:44 #14 QPixmapIconEngine::addFile (this=0x555556112970, fileName=<optimized out>, size=..., mode=QIcon::Normal, state=QIcon::Off) at /usr/src/debug/qt6-base/qtbase/src/gui/image/qicon.cpp:460 #15 0x00007ffff35153b6 in QIcon::addFile (this=this@entry=0x7fffffffb208, fileName=..., size=..., mode=mode@entry=QIcon::Normal, state=state@entry=QIcon::Off) at /usr/src/debug/qt6-base/qtbase/src/gui/image/qicon.cpp:1195 #16 0x00007ffff40384c3 in operator() (prefix=..., icon=..., __closure=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qfusionstyle.cpp:3509 #17 0x00007ffff4038e0c in QFusionStyle::iconFromTheme (this=this@entry=0x555555cb8ec0, standardIcon=standardIcon@entry=QStyle::SP_TitleBarNormalButton) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qfusionstyle.cpp:3514 #18 0x00007ffff4038e69 in QFusionStyle::standardIcon (this=0x555555cb8ec0, standardIcon=QStyle::SP_TitleBarNormalButton, option=0x7fffffffbcf0, widget=0x55555611c7b0) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qfusionstyle.cpp:3538 #19 0x00007ffff3fb2711 in QProxyStyle::standardIcon (this=<optimized out>, standardIcon=QStyle::SP_TitleBarNormalButton, option=0x7fffffffbcf0, widget=0x55555611c7b0) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qproxystyle.cpp:380 #20 0x00007ffff3f8c27d in QCommonStyle::subElementRect (this=<optimized out>, sr=<optimized out>, opt=0x7fffffffbcf0, widget=0x55555611c7b0) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qcommonstyle.cpp:3074 #21 0x00007ffff4038145 in QFusionStyle::subElementRect (this=<optimized out>, sr=QStyle::SE_DockWidgetTitleBarText, opt=0x7fffffffbcf0, w=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qfusionstyle.cpp:3467 #22 0x00007ffff400855b in QStyleSheetStyle::subElementRect (this=0x555555ce9b60, se=QStyle::SE_DockWidgetTitleBarText, opt=0x7fffffffbcf0, w=0x55555611c7b0) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qstylesheetstyle.cpp:6402 #23 0x00007ffff3ffb3c0 in QStyleSheetStyle::drawControl (this=0x555555ce9b60, ce=<optimized out>, opt=0x7fffffffbcf0, p=0x7fffffffbcd0, w=0x55555611c7b0) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qstylesheetstyle.cpp:4442 #24 0x00007ffff409758e in QStylePainter::drawControl (this=0x7fffffffbcd0, ce=QStyle::CE_DockWidgetTitle, opt=...) at /usr/src/debug/qt6-base/qtbase/src/widgets/styles/qstylepainter.h:51 #25 QDockWidget::paintEvent (this=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/widgets/widgets/qdockwidget.cpp:1633 #26 0x00007ffff3f5d6f3 in QWidget::event (this=0x55555611c7b0, event=0x7fffffffbf40) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:9134 #27 0x00007ffff3f020a0 in QApplicationPrivate::notify_helper (this=<optimized out>, receiver=0x55555611c7b0, e=0x7fffffffbf40) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qapplication.cpp:3307 #28 0x00007ffff2d6a6c8 in QCoreApplication::notifyInternal2 (receiver=0x55555611c7b0, event=0x7fffffffbf40) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109 #29 0x00007ffff2d6a71d in QCoreApplication::sendSpontaneousEvent (receiver=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/corelib/kernel/qcoreapplication.cpp:1563 #30 0x00007ffff3f4e96e in QWidgetPrivate::sendPaintEvent (this=this@entry=0x5555561c6380, toBePainted=...) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:5661 #31 0x00007ffff3f50c27 in QWidgetPrivate::drawWidget (this=this@entry=0x5555561c6380, pdev=pdev@entry=0x555557df9428, rgn=..., offset=..., flags=flags@entry=..., sharedPainter=sharedPainter@entry=0x0, repaintManager=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:5611 #32 0x00007ffff3f53a64 in QWidgetPrivate::paintSiblingsRecursive (this=this@entry=0x555555c6ee10, pdev=pdev@entry=0x555557df9428, siblings=<optimized out>, index=<optimized out>, rgn=..., offset=..., flags=..., sharedPainter=0x0, repaintManager=0x555558857670) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:5790 #33 0x00007ffff3f51185 in QWidgetPrivate::drawWidget (this=0x555555c6ee10, pdev=0x555557df9428, rgn=<optimized out>, offset=<optimized out>, flags=..., sharedPainter=<optimized out>, repaintManager=<optimized out>) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:5652 #34 0x00007ffff3f75929 in QWidgetRepaintManager::paintAndFlush (this=0x555558857670) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidgetrepaintmanager.cpp:907 #35 0x00007ffff3f5d0f6 in QWidget::event (this=0x555555f04720, event=0x555555d326e0) at /usr/src/debug/qt6-base/qtbase/src/widgets/kernel/qwidget.cpp:9298
OBS is slow
Not your fault, but OBS calls QWidget::setAttribute(Qt::WA_OpaquePaintEvent)... on a volume meter widget repainted at 60fps, even though it's assigned a transparent background by stylesheets. Qt notices it's not opaque, and turns off the attribute upon polish, so now the QDockWidget holding the volume meter is painted at 60fps too.
So far this is normal. The problem is that even if I change it to an opaque background, QStyleSheetStyle::polish() still turns off WA_OpaquePaintEvent, so I have to override QWidget::event() to force it back on after QEvent::Polish. Not sure why you turn it off (didn't poke that code with a debugger) maybe you require an opaque border which shouldn't be necessary?
Building QIcon in QStyle::subElementRect is slow
Repainting QDockWidget calls QStyle::subElementRect(SE_DockWidgetTitleBarText), which relies on constructing a QIcon on every repaint to figure out how much size to reserve for buttons. This can be expensive even without other bugs involved.
It may be faster to add a QDockWidgetLayout item for title bar text, make the layout compute the text rectangle, then QCommonStyle::subElementRect() would look it up. Alternatively subElementRect() could get a reference to the dwLayout->widgetForRole(QDockWidgetLayout::FloatButton) created in QDockWidgetPrivate::updateButtons().
- This change is simple, but fully eliminating icon lookup during painting is a bigger task, since qcommonstyle.cpp has 19 occurrences of string proxy()->standardIcon!
Incorrect QDockWidget icon names
QCommonStyle::subElementRect calls to QFusionStyle::iconFromTheme to obtain a restore icon and measure its size. In the latter function, line 3500-3530 builds a QIcon and adds various sizes of image from the ":/qt-project.org/styles/fusionstyle/images/" resource path.
- Because we're passed SP_TitleBarNormalButton, this looks up ":/qt-project.org/styles/fusionstyle/images/fusion_normalizedockup-(sizes).png".
- Many of the actual files have underscores instead of hyphens.
I've made a test C++/Qt project at codeberg.org/nyanpasu64/fusion-resources, which confirms that QFile(":/qt-project.org/styles/fusionstyle/images/fusion_normalizedockup-%d.png") fails to open for %d != 16 or 32.
To fix this issue, you'd have to rename "fusion_normalizedockup_(sizes).png" to hyphens, because that's where QFusionStyle::iconFromTheme looks.
QFile::open() on a missing resource path repeatedly looks up path
- In any case, Windows still invokes QImageReaderPrivate::initHandler, where Linux burns CPU at line 513. Windows never actually reaches the corresponding line 537. We do reach line 510 which invokes device->open(QIODevice::ReadOnly).
- QFile::open at line 926 invokes QResourceFileEngine::open.
- QResourceFileEngine implements QAbstractFileEngine, which effectively implements a file descriptor.
- QResourceFileEngine::open at line 1431+ owns a single QResource (docs, it's basically a virtual file/folder inode).
- Every operation on a QResource invokes QResourcePrivate::ensureInitialized.
- This eventually calls to QResourcePrivate::load -> QResourceRoot::findNode.
- If QResourcePrivate::load succeeds, it sets related.append(res);. Subsequent calls to QResourcePrivate::ensureInitialized() will see if (!related.isEmpty()) and return immediately.
- If it fails, we never write down "file not found", every single subsequent attempt to QResourcePrivate::ensureInitialized wastes CPU on the same failing lookup, making up the vast majority of CPU time seen in the profiler trace. And QFile::open() performs many such failing operations, making an invalid path several times as expensive as a valid one!
To prevent this CPU burn, it may be necessary to record a "load failed" flag in addition to pushing to related, or make QFile::open() on a resource path bail out as soon as it's not found.