I've been struggling lately when i had to change some code that i wrote a while ago to do image processing in Qt
and OpenGl
to support multithreading.
the problem is that I want to use it to apply batches of filters on a set of images, i'm using openMP to do the multi-threading like this:
void batchProcess(QVector<QImage> &images)
{
#pragma omp parallel
{
#pragma omp for schedule(dynamic) nowait
for (int i = 1; i < images.count(); i++)
{
images[i] = ImageProcessing::processImage(images[i], _vertexShader, _fragmentShader, _textureVar, _vertexPosVar, _textureCoordVar);
}
}
}
and the ImageProcessing::processImage()
function looks like this:
QImage ImageProcessing::processImage(const QImage &source, const QString &vertexShader, const QString &fragmentShader, const QString &textureVar, const QString &vertexPosVar, const QString &textureCoordVar)
{
QOpenGLContext context;
if(!context.create())
return source;
QOffscreenSurface surface;
surface.setFormat(context.format());
surface.create();
if(!surface.isValid())
return source;
if(!context.makeCurrent(&surface))
return source;
QOpenGLFramebufferObject fbo(source.size());
context.functions()->glViewport(0, 0, source.width(), source.height());
QOpenGLShaderProgram program(&context);
if (!program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader))
return source;
if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader))
return source;
if (!program.link())
return source;
if (!program.bind())
return source;
QOpenGLTexture texture(QOpenGLTexture::Target2D);
texture.setData(source);
texture.bind();
if(!texture.isBound())
return source;
VertexData vertices[] =
{
{{ -1.0f, +1.0f }, { 0.0f, 1.0f }}, // top-left
{{ +1.0f, +1.0f }, { 1.0f, 1.0f }}, // top-right
{{ -1.0f, -1.0f }, { 0.0f, 0.0f }}, // bottom-left
{{ +1.0f, -1.0f }, { 1.0f, 0.0f }} // bottom-right
};
GLuint indices[] =
{
0, 1, 2, 3
};
QOpenGLBuffer vertexBuf(QOpenGLBuffer::VertexBuffer);
QOpenGLBuffer indexBuf(QOpenGLBuffer::IndexBuffer);
if(!vertexBuf.create())
return source;
if(!indexBuf.create())
return source;
if(!vertexBuf.bind())
return source;
vertexBuf.allocate(vertices, 4 * sizeof(VertexData));
if(!indexBuf.bind())
return source;
indexBuf.allocate(indices, 4 * sizeof(GLuint));
int offset = 0;
program.enableAttributeArray(vertexPosVar.toLatin1().data());
program.setAttributeBuffer(vertexPosVar.toLatin1().data(), GL_FLOAT, offset, 2, sizeof(VertexData));
offset += sizeof(QVector2D);
program.enableAttributeArray(textureCoordVar.toLatin1().data());
program.setAttributeBuffer(textureCoordVar.toLatin1().data(), GL_FLOAT, offset, 2, sizeof(VertexData));
program.setUniformValue(textureVar.toLatin1().data(), 0);
context.functions()->glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, Q_NULLPTR);
return fbo.toImage(false);
}
it works fine when OpenMP
is disabled, but when i enable it i get the following error:
Attempting to create QWindow-based QOffscreenSurface outside the gui thread. Expect failures.
according to the documentation, this is an expected behavior, because the QOffscreenSurface
is just a hidden QWindow
on some platforms, which means that it can only be created or destroyed from the main thread.
so in order to create the QOffscreenSurface
in the Main thread i followed this answer and implemented the following class
class OpenGlFilterPrivate : public QObject
{
Q_OBJECT
public:
enum CustomEventTypes
{
CreateSurface = QEvent::User,
CreateContext = QEvent::User + 1,
CreateProgram = QEvent::User + 2
};
void moveToMainThread()
{
moveToThread(QApplication::instance()->thread());
}
virtual bool event(QEvent *event )
{
switch (event->type())
{
case CreateSurface: // m_surface (create an offscreen surface from the main thread)
m_surface = QSharedPointer<QOffscreenSurface>::create();
m_surface->setFormat(m_context->format());
m_surface->create();
break;
case CreateContext: // m_context (create an openGl context from the main thread)
m_context = QSharedPointer<QOpenGLContext>::create();
break;
case CreateProgram: // m_shaderProgram (create an openGl shader program from the main thread)
m_shaderProgram = QSharedPointer<QOpenGLShaderProgram>::create(&*m_context);
}
return false;
}
QSharedPointer<QOpenGLContext> m_context;
QSharedPointer<QOffscreenSurface> m_surface;
QSharedPointer<QOpenGLShaderProgram> m_shaderProgram;
};
and now instead of directly creating a QOffscreenSurface
in my processImage
function i do the following:
OpenGlFilterPrivate openGlFilterPrivate;
openGlFilterPrivate.moveToMainThread();
QCoreApplication::postEvent(&openGlFilterPrivate, new QEvent(QEvent::Type(OpenGlFilterPrivate::CreateSurface)));
QSharedPointer<QOffscreenSurface> surface = openGlFilterPrivate.m_surface;
it works, but now i keep getting the following message:
QObject::~QObject: Timers cannot be stopped from another thread
and then my application crashes immediately.
i know that any QObject
contains a QTimer
internally and that this is what's causing the problem, but i can't think of any other way to get around this.
Can anyone help with this?
all i need to do is to apply some image processing filters using openGl
or any other hardware accelerated method and at the same time have the ability to do it in a thread-safe
way.
- i think it can be done using the same method used by
QPainter
, it's thread safe and as far as i know it's hardware accelerated using OpenGl. but i couldn't find any resources on how to do such thing.