display: fix ability to make screenshots in multithreaded pipeline

Problem is that WGL is strict about binding context in different thread while it is still bound in another thread.  Either way we need to make sure the draw thread is not rendering, so if you call get_screenshot() from a thread other than the draw thread, it uses the GraphicsEngine to wait until the draw thread is idle and then asks it to do the get_screenshot().

Fixes #360
This commit is contained in:
rdb 2018-07-05 19:02:37 +02:00
parent b85fead09d
commit d4d582484f
3 changed files with 54 additions and 1 deletions

View File

@ -483,6 +483,14 @@ get_screenshot() {
GraphicsStateGuardian *gsg = window->get_gsg();
nassertr(gsg != nullptr, nullptr);
// Are we on the draw thread?
if (gsg->get_threading_model().get_draw_stage() != current_thread->get_pipeline_stage()) {
// Ask the engine to do on the draw thread.
GraphicsEngine *engine = window->get_engine();
return engine->do_get_screenshot(this, gsg);
}
// We are on the draw thread.
if (!window->begin_frame(GraphicsOutput::FM_refresh, current_thread)) {
return nullptr;
}

View File

@ -1249,6 +1249,43 @@ texture_uploaded(Texture *tex) {
// Usually only called by DisplayRegion::do_cull.
}
/**
* Called by DisplayRegion::do_get_screenshot
*/
PT(Texture) GraphicsEngine::
do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg) {
// A multi-threaded environment. We have to wait until the draw thread
// has finished its current task.
ReMutexHolder holder(_lock);
const std::string &draw_name = gsg->get_threading_model().get_draw_name();
WindowRenderer *wr = get_window_renderer(draw_name, 0);
RenderThread *thread = (RenderThread *)wr;
MutexHolder cv_holder(thread->_cv_mutex);
while (thread->_thread_state != TS_wait) {
thread->_cv_done.wait();
}
// Now that the draw thread is idle, signal it to do the extraction task.
thread->_region = region;
thread->_thread_state = TS_do_screenshot;
thread->_cv_start.notify();
thread->_cv_mutex.release();
thread->_cv_mutex.acquire();
//XXX is this necessary, or is acquiring the mutex enough?
while (thread->_thread_state != TS_wait) {
thread->_cv_done.wait();
}
PT(Texture) tex = std::move(thread->_texture);
thread->_region = nullptr;
thread->_texture = nullptr;
return tex;
}
/**
* Fires off a cull traversal using the indicated camera.
*/
@ -2633,6 +2670,11 @@ thread_main() {
_result = _gsg->extract_texture_data(_texture);
break;
case TS_do_screenshot:
nassertd(_region != nullptr) break;
_texture = _region->get_screenshot();
break;
case TS_terminate:
do_pending(_engine, current_thread);
do_close(_engine, current_thread);

View File

@ -125,11 +125,13 @@ public:
TS_do_windows,
TS_do_compute,
TS_do_extract,
TS_do_screenshot,
TS_terminate,
TS_done
};
void texture_uploaded(Texture *tex);
PT(Texture) do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg);
public:
static void do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
@ -304,8 +306,9 @@ private:
// These are stored for extract_texture_data and dispatch_compute.
GraphicsStateGuardian *_gsg;
Texture *_texture;
PT(Texture) _texture;
const RenderState *_state;
DisplayRegion *_region;
LVecBase3i _work_groups;
bool _result;
};