PDA

View Full Version : A worker thread with OpenGL support, am I doing it right?



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());
}
}

wysota
21st March 2014, 15:09
Why not load all the data in a worker thread and then push it to the GPU in the main thread?

jelko
21st March 2014, 15:33
That is to keep the GUI thread responsive, uploading the vertex buffers to the GPU might take 5 - 6 seconds as wel (depending on your machine).

wysota
21st March 2014, 23:15
Then I don't see how you can improve what you already have :)