Make multi-threaded image transforms and painter fills configurable

Some users prefer to avoid having this many threads. This also
moves disabling it for WASM from sources to config.

Fixes: QTBUG-129650
Change-Id: Ib4c7903e85ba9cb75a9e013d1032653ea0ab8b84
Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
This commit is contained in:
Allan Sandfeld Jensen 2024-11-15 10:44:05 +01:00
parent 154fe8d803
commit 083e44318c
13 changed files with 97 additions and 72 deletions

View File

@ -930,10 +930,8 @@ QCoreApplication::~QCoreApplication()
#if QT_CONFIG(thread)
// Synchronize and stop the global thread pool threads.
QThreadPool *globalThreadPool = nullptr;
QThreadPool *guiThreadPool = nullptr;
QT_TRY {
globalThreadPool = QThreadPool::globalInstance();
guiThreadPool = QThreadPoolPrivate::qtGuiInstance();
} QT_CATCH (...) {
// swallow the exception, since destructors shouldn't throw
}
@ -941,10 +939,6 @@ QCoreApplication::~QCoreApplication()
globalThreadPool->waitForDone();
delete globalThreadPool;
}
if (guiThreadPool) {
guiThreadPool->waitForDone();
delete guiThreadPool;
}
#endif
#ifndef QT_NO_QOBJECT

View File

@ -474,28 +474,6 @@ QThreadPool *QThreadPool::globalInstance()
return theInstance;
}
/*!
Returns the QThreadPool instance for Qt Gui.
\internal
*/
QThreadPool *QThreadPoolPrivate::qtGuiInstance()
{
Q_CONSTINIT static QPointer<QThreadPool> guiInstance;
Q_CONSTINIT static QBasicMutex theMutex;
const static bool runtime_disable = qEnvironmentVariableIsSet("QT_NO_GUI_THREADPOOL");
if (runtime_disable)
return nullptr;
const QMutexLocker locker(&theMutex);
if (guiInstance.isNull() && !QCoreApplication::closingDown()) {
guiInstance = new QThreadPool();
// Limit max thread to avoid too many parallel threads.
// We are not optimized for much more than 4 or 8 threads.
if (guiInstance && guiInstance->maxThreadCount() > 4)
guiInstance->setMaxThreadCount(qBound(4, guiInstance->maxThreadCount() / 2, 8));
}
return guiInstance;
}
/*!
Reserves a thread and uses it to run \a runnable, unless this thread will
make the current thread count exceed maxThreadCount(). In that case,

View File

@ -133,8 +133,6 @@ public:
void stealAndRunRunnable(QRunnable *runnable);
void deletePageIfFinished(QueuePage *page);
static QThreadPool *qtGuiInstance();
mutable QMutex mutex;
QSet<QThreadPoolThread *> allThreads;
QQueue<QThreadPoolThread *> waitingThreads;

View File

@ -1271,6 +1271,14 @@ qt_feature("raster-fp" PRIVATE
PURPOSE "Internal painting support for floating point rasterization."
CONDITION NOT VXWORKS # QTBUG-115777
)
qt_feature("qtgui-threadpool" PRIVATE
SECTION "Painting"
LABEL "Multi-threaded image and painting helpers"
PURPOSE "Multi-threaded image transforms and QPainter fills."
CONDITION QT_FEATURE_thread AND NOT WASM
)
qt_feature("undocommand" PUBLIC
SECTION "Utilities"
LABEL "QUndoCommand"
@ -1344,6 +1352,10 @@ qt_configure_add_summary_entry(ARGS "vulkan")
qt_configure_add_summary_entry(ARGS "metal")
qt_configure_add_summary_entry(ARGS "graphicsframecapture")
qt_configure_add_summary_entry(ARGS "sessionmanager")
qt_configure_add_summary_entry(
ARGS "qtgui-threadpool"
CONDITION QT_FEATURE_thread
)
qt_configure_end_summary_section() # end of "Qt Gui" section
qt_configure_add_summary_section(NAME "Features used by QPA backends")
qt_configure_add_summary_entry(ARGS "evdev")

View File

@ -35,7 +35,7 @@
#include <private/qimage_p.h>
#include <private/qfont_p.h>
#if QT_CONFIG(thread)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qthreadpool_p.h>
@ -4908,7 +4908,7 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
}
// Otherwise only use it when the scaling factor demands it, or the image is large enough to scale multi-threaded
if (nonpaintable_scale_xform
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
|| (ws * hs) >= (1<<20)
#endif
) {
@ -5328,10 +5328,10 @@ void QImage::applyColorTransform(const QColorTransform &transform)
};
}
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(width()) * height()) >> 16;
segments = std::min(segments, height());
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;
@ -5818,10 +5818,10 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
}
}
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(width()) * height()) >> 16;
segments = std::min(segments, height());
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;

View File

@ -17,11 +17,6 @@
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qthreadpool_p.h>
#ifdef Q_OS_WASM
// WebAssembly has threads; however we can't block the main thread.
#else
#define QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#endif
#endif
#include <QtCore/q20utility.h>
@ -212,11 +207,11 @@ void convert_generic(QImageData *dest, const QImageData *src, Qt::ImageConversio
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(src->width) * src->height) >> 16;
segments = std::min(segments, src->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments <= 1 || !threadPool || threadPool->contains(QThread::currentThread()))
return convertSegment(0, src->height);
@ -267,11 +262,11 @@ void convert_generic_over_rgb64(QImageData *dest, const QImageData *src, Qt::Ima
destData += dest->bytes_per_line;
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(src->width) * src->height) >> 16;
segments = std::min(segments, src->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments <= 1 || !threadPool || threadPool->contains(QThread::currentThread()))
return convertSegment(0, src->height);
@ -321,11 +316,11 @@ void convert_generic_over_rgba32f(QImageData *dest, const QImageData *src, Qt::I
destData += dest->bytes_per_line;
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(src->width) * src->height) >> 16;
segments = std::min(segments, src->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments <= 1 || !threadPool || threadPool->contains(QThread::currentThread()))
return convertSegment(0, src->height);
@ -434,10 +429,10 @@ bool convert_generic_inplace(QImageData *data, QImage::Format dst_format, Qt::Im
destData += params.bytesPerLine;
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(data->width) * data->height) >> 16;
segments = std::min(segments, data->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;
@ -527,10 +522,10 @@ bool convert_generic_inplace_over_rgb64(QImageData *data, QImage::Format dst_for
destData += params.bytesPerLine;
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(data->width) * data->height) >> 16;
segments = std::min(segments, data->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;
@ -621,10 +616,10 @@ bool convert_generic_inplace_over_rgba32f(QImageData *data, QImage::Format dst_f
destData += params.bytesPerLine;
}
};
#ifdef QT_USE_THREAD_PARALLEL_IMAGE_CONVERSIONS
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(data->width) * data->height) >> 16;
segments = std::min(segments, data->height);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;

View File

@ -99,6 +99,10 @@
#include <private/qvulkandefaultinstance_p.h>
#endif
#if QT_CONFIG(thread)
#include <QtCore/QThreadPool>
#endif
#include <qtgui_tracepoints_p.h>
#include <private/qtools_p.h>
@ -686,6 +690,20 @@ QGuiApplication::~QGuiApplication()
d->cursor_list.clear();
#endif
#if QT_CONFIG(qtgui_threadpool)
// Synchronize and stop the gui thread pool threads.
QThreadPool *guiThreadPool = nullptr;
QT_TRY {
guiThreadPool = QGuiApplicationPrivate::qtGuiThreadPool();
} QT_CATCH (...) {
// swallow the exception, since destructors shouldn't throw
}
if (guiThreadPool) {
guiThreadPool->waitForDone();
delete guiThreadPool;
}
#endif
delete QGuiApplicationPrivate::app_icon;
QGuiApplicationPrivate::app_icon = nullptr;
delete QGuiApplicationPrivate::platform_name;
@ -4552,6 +4570,32 @@ QInputDeviceManager *QGuiApplicationPrivate::inputDeviceManager()
return m_inputDeviceManager;
}
/*!
Returns the QThreadPool instance for Qt Gui.
\internal
*/
QThreadPool *QGuiApplicationPrivate::qtGuiThreadPool()
{
#if QT_CONFIG(qtgui_threadpool)
Q_CONSTINIT static QPointer<QThreadPool> guiInstance;
Q_CONSTINIT static QBasicMutex theMutex;
const static bool runtime_disable = qEnvironmentVariableIsSet("QT_NO_GUI_THREADPOOL");
if (runtime_disable)
return nullptr;
const QMutexLocker locker(&theMutex);
if (guiInstance.isNull() && !QCoreApplication::closingDown()) {
guiInstance = new QThreadPool();
// Limit max thread to avoid too many parallel threads.
// We are not optimized for much more than 4 or 8 threads.
if (guiInstance && guiInstance->maxThreadCount() > 4)
guiInstance->setMaxThreadCount(qBound(4, guiInstance->maxThreadCount() / 2, 8));
}
return guiInstance;
#else
return nullptr;
#endif
}
/*!
\fn template <typename QNativeInterface> QNativeInterface *QGuiApplication::nativeInterface() const

View File

@ -57,6 +57,7 @@ class QActionPrivate;
#if QT_CONFIG(shortcut)
class QShortcutPrivate;
#endif
class QThreadPool;
class Q_GUI_EXPORT QGuiApplicationPrivate : public QCoreApplicationPrivate
{
@ -337,6 +338,8 @@ public:
static QEvent::Type contextMenuEventType();
static QThreadPool *qtGuiThreadPool();
protected:
virtual void handleThemeChanged();

View File

@ -27,11 +27,7 @@
#include <qloggingcategory.h>
#include <qmath.h>
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#define QT_USE_THREAD_PARALLEL_FILLS
#endif
#if defined(QT_USE_THREAD_PARALLEL_FILLS)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qthreadpool_p.h>
@ -3963,10 +3959,10 @@ static void spanfill_from_first(QRasterBuffer *rasterBuffer, QPixelLayout::BPP b
// -------------------- blend methods ---------------------
#if defined(QT_USE_THREAD_PARALLEL_FILLS)
#if QT_CONFIG(qtgui_threadpool)
#define QT_THREAD_PARALLEL_FILLS(function) \
const int segments = (count + 32) / 64; \
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance(); \
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool(); \
if (segments > 1 && qPixelLayouts[data->rasterBuffer->format].bpp >= QPixelLayout::BPP8 \
&& threadPool && !threadPool->contains(QThread::currentThread())) { \
QSemaphore semaphore; \

View File

@ -10,9 +10,10 @@
#include "qrgba64_p.h"
#include "qrgbafloat.h"
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qguiapplication_p.h>
#include <private/qthreadpool_p.h>
#endif
@ -284,10 +285,10 @@ void qt_qimageScaleAARGBA_down_xy_neon(QImageScaleInfo *isi, unsigned int *dest,
template<typename T>
static inline void multithread_pixels_function(QImageScaleInfo *isi, int dh, const T &scaleSection)
{
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(isi->sh) * isi->sw) / (1<<16);
segments = std::min(segments, dh);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;

View File

@ -6,8 +6,9 @@
#include <private/qdrawhelper_loongarch64_p.h>
#include <private/qsimd_p.h>
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <private/qguiapplication_p.h>
#include <private/qthreadpool_p.h>
#endif
@ -20,10 +21,10 @@ using namespace QImageScale;
template<typename T>
static inline void multithread_pixels_function(QImageScaleInfo *isi, int dh, const T &scaleSection)
{
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(isi->sh) * isi->sw) / (1<<16);
segments = std::min(segments, dh);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;

View File

@ -3,11 +3,13 @@
#include "qimagescale_p.h"
#include "qimage.h"
#include <private/qtguiglobal_p.h>
#include <private/qsimd_p.h>
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qguiapplication_p.h>
#include <private/qthreadpool_p.h>
#endif
@ -20,10 +22,10 @@ using namespace QImageScale;
template<typename T>
static inline void multithread_pixels_function(QImageScaleInfo *isi, int dh, const T &scaleSection)
{
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(isi->sh) * isi->sw) / (1<<16);
segments = std::min(segments, dh);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;

View File

@ -6,9 +6,10 @@
#include <private/qdrawhelper_x86_p.h>
#include <private/qsimd_p.h>
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
#include <qsemaphore.h>
#include <qthreadpool.h>
#include <private/qguiapplication_p.h>
#include <private/qthreadpool_p.h>
#endif
@ -21,10 +22,10 @@ using namespace QImageScale;
template<typename T>
static inline void multithread_pixels_function(QImageScaleInfo *isi, int dh, const T &scaleSection)
{
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
#if QT_CONFIG(qtgui_threadpool)
int segments = (qsizetype(isi->sh) * isi->sw) / (1<<16);
segments = std::min(segments, dh);
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
QThreadPool *threadPool = QGuiApplicationPrivate::qtGuiThreadPool();
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
QSemaphore semaphore;
int y = 0;