From d4d582484fff0ddfdd1ec03e80dfb2cfd66da66d Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 5 Jul 2018 19:02:37 +0200 Subject: [PATCH] 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 --- panda/src/display/displayRegion.cxx | 8 ++++++ panda/src/display/graphicsEngine.cxx | 42 ++++++++++++++++++++++++++++ panda/src/display/graphicsEngine.h | 5 +++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/panda/src/display/displayRegion.cxx b/panda/src/display/displayRegion.cxx index e32b0f5a50..e1765861e1 100644 --- a/panda/src/display/displayRegion.cxx +++ b/panda/src/display/displayRegion.cxx @@ -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; } diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 0b4e8448b1..dd637d7a9a 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -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); diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index 667490b3d3..3371f20b39 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -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; };