jelko
21st March 2014, 11:17
Ik work on an application where it's possible to load very large CAD files, loading can take quite some time, so to keep the UI responsive loading is done in a workerthread.
The workerthread requires an active OpenGL context as well, so it can upload vertexbuffers to the GPU. I came up with the following solution.
- in main thread first make current context not current
- store pointer to surface
- create Worker Object and pass pointers to surface and context
- change thread affinity of context (moveToThread)
- start workerthread
- make context current in workerthread
- do work
- when work is finished call doneCurrent (make not current)
- restore thread affinity
- make context active again in main thread
It requires some hassle but it works, I am wondering how other applications deal with this problem?
/// <summary>Starts a workerthread and handles its progress events</summary>
int ProgressHandler::handleWork(boost::function<int(void)>& functor, bool requiresRenderContext/* = false*/)
{
// careful only call this method from the main thread
//ASSERT(GetCurrentThreadId() == theApp.GetThreadId());
QOpenGLContext* context = nullptr;
QSurface* surface = nullptr;
// make current OpenGL context not current if required, this is needed to be able to make it current in the workerthread
if (requiresRenderContext)
{
context = QOpenGLContext::currentContext();
surface = context->surface();
context->doneCurrent();
}
// create a Worker object
int result = 0;
deskproto::Worker work(functor, result, context, surface);
context->moveToThread(&work);
connect(&work, &QThread::finished, this, &ProgressHandler::onWorkerFinished);
// start workerthread
work.start();
// enter modal loop
m_progressDialog.exec();
// wait until thread finishes
work.wait();
// make RenderContext current again for main thread
if (requiresRenderContext)
{
bool result = context->makeCurrent(surface);
assert(result);
}
// re-throw any uncaught exception in main thread
work.rethrow();
return result;
}
/// <summary>
/// SwapContext allows to make an OpenGL context active for a different thread without changing the function
/// e.g. used for loading 3D geometry, plotdata and bitmapdata in a workerthread (functions which require an active OpenGL context)
/// </summary>
void Worker::run()
{
#ifdef _DEBUG
util::SetThreadName("Worker");
#endif
// Make context active for current thread, only works if previous context is made not current
if (m_context)
{
m_context->makeCurrent(m_surface);
}
try
{
// Call the functor object
m_result = m_functor();
}
catch(...)
{
// catch any uncaught exceptions and re-trow them on main thread
Worker::m_exception = std::current_exception();
}
// Make context not current(restore)
if (m_context)
{
m_context->doneCurrent();
m_context->moveToThread(QApplication::instance()->thread());
}
}
The workerthread requires an active OpenGL context as well, so it can upload vertexbuffers to the GPU. I came up with the following solution.
- in main thread first make current context not current
- store pointer to surface
- create Worker Object and pass pointers to surface and context
- change thread affinity of context (moveToThread)
- start workerthread
- make context current in workerthread
- do work
- when work is finished call doneCurrent (make not current)
- restore thread affinity
- make context active again in main thread
It requires some hassle but it works, I am wondering how other applications deal with this problem?
/// <summary>Starts a workerthread and handles its progress events</summary>
int ProgressHandler::handleWork(boost::function<int(void)>& functor, bool requiresRenderContext/* = false*/)
{
// careful only call this method from the main thread
//ASSERT(GetCurrentThreadId() == theApp.GetThreadId());
QOpenGLContext* context = nullptr;
QSurface* surface = nullptr;
// make current OpenGL context not current if required, this is needed to be able to make it current in the workerthread
if (requiresRenderContext)
{
context = QOpenGLContext::currentContext();
surface = context->surface();
context->doneCurrent();
}
// create a Worker object
int result = 0;
deskproto::Worker work(functor, result, context, surface);
context->moveToThread(&work);
connect(&work, &QThread::finished, this, &ProgressHandler::onWorkerFinished);
// start workerthread
work.start();
// enter modal loop
m_progressDialog.exec();
// wait until thread finishes
work.wait();
// make RenderContext current again for main thread
if (requiresRenderContext)
{
bool result = context->makeCurrent(surface);
assert(result);
}
// re-throw any uncaught exception in main thread
work.rethrow();
return result;
}
/// <summary>
/// SwapContext allows to make an OpenGL context active for a different thread without changing the function
/// e.g. used for loading 3D geometry, plotdata and bitmapdata in a workerthread (functions which require an active OpenGL context)
/// </summary>
void Worker::run()
{
#ifdef _DEBUG
util::SetThreadName("Worker");
#endif
// Make context active for current thread, only works if previous context is made not current
if (m_context)
{
m_context->makeCurrent(m_surface);
}
try
{
// Call the functor object
m_result = m_functor();
}
catch(...)
{
// catch any uncaught exceptions and re-trow them on main thread
Worker::m_exception = std::current_exception();
}
// Make context not current(restore)
if (m_context)
{
m_context->doneCurrent();
m_context->moveToThread(QApplication::instance()->thread());
}
}