浏览代码

display: Generalized method for running things on draw thread

This is more general and elegant than manually listing all operations in
a big enum
rdb 10 月之前
父节点
当前提交
67723ca06a

+ 3 - 1
panda/src/display/displayRegion.cxx

@@ -487,7 +487,9 @@ get_screenshot() {
   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);
+    return engine->run_on_draw_thread([this] {
+      return get_screenshot();
+    });
   }
 
   // We are on the draw thread.

+ 50 - 0
panda/src/display/graphicsEngine.I

@@ -171,3 +171,53 @@ INLINE void GraphicsEngine::
 dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg) {
   dispatch_compute(work_groups, RenderState::make(sattr), gsg);
 }
+
+#ifndef CPPPARSER
+/**
+ * Waits for the draw thread to become idle, then runs the given function on it.
+ */
+template<class Callable>
+INLINE auto GraphicsEngine::
+run_on_draw_thread(Callable &&callable) -> decltype(callable()) {
+  ReMutexHolder holder(_lock);
+  std::string draw_name = _threading_model.get_draw_name();
+  if (draw_name.empty()) {
+    return std::move(callable)();
+  } else {
+    WindowRenderer *wr = get_window_renderer(draw_name, 0);
+    RenderThread *thread = (RenderThread *)wr;
+    return thread->run_on_thread(std::move(callable));
+  }
+}
+
+/**
+ * Waits for this thread to become idle, then runs the given function on it.
+ */
+template<class Callable>
+INLINE auto GraphicsEngine::RenderThread::
+run_on_thread(Callable &&callable) ->
+  typename std::enable_if<!std::is_void<decltype(callable())>::value, decltype(callable())>::type {
+
+  using ReturnType = decltype(callable());
+  alignas(ReturnType) unsigned char storage[sizeof(ReturnType)];
+
+  run_on_thread([] (RenderThread *data) {
+    new (data->_return_data) ReturnType(std::move(*(Callable *)data->_callback_data)());
+  }, &callable, storage);
+
+  return *(ReturnType *)storage;
+}
+
+/**
+ * Waits for this thread to become idle, then runs the given function on it.
+ */
+template<class Callable>
+INLINE auto GraphicsEngine::RenderThread::
+run_on_thread(Callable &&callable) ->
+  typename std::enable_if<std::is_void<decltype(callable())>::value, decltype(callable())>::type {
+
+  run_on_thread([] (RenderThread *data) {
+    std::move(*(Callable *)data->_callback_data)();
+  }, &callable, nullptr);
+}
+#endif  // CPPPARSER

+ 45 - 187
panda/src/display/graphicsEngine.cxx

@@ -1134,47 +1134,9 @@ flip_frame() {
  */
 bool GraphicsEngine::
 extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) {
-  ReMutexHolder holder(_lock);
-
-  string draw_name = gsg->get_threading_model().get_draw_name();
-  if (draw_name.empty()) {
-    // A single-threaded environment.  No problem.
+  return run_on_draw_thread([=] () {
     return gsg->extract_texture_data(tex);
-
-  } else {
-    // A multi-threaded environment.  We have to wait until the draw thread
-    // has finished its current task.
-    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();
-    }
-
-    // Temporarily set this so that it accesses data from the current thread.
-    int pipeline_stage = Thread::get_current_pipeline_stage();
-    int draw_pipeline_stage = thread->get_pipeline_stage();
-    thread->set_pipeline_stage(pipeline_stage);
-
-    // Now that the draw thread is idle, signal it to do the extraction task.
-    thread->_gsg = gsg;
-    thread->_texture = tex;
-    thread->_thread_state = TS_do_extract_texture_data;
-    thread->_cv_mutex.release();
-    thread->_cv_start.notify();
-    thread->_cv_mutex.acquire();
-
-    // Wait for it to finish the extraction.
-    while (thread->_thread_state != TS_wait) {
-      thread->_cv_done.wait();
-    }
-
-    thread->set_pipeline_stage(draw_pipeline_stage);
-    thread->_gsg = nullptr;
-    thread->_texture = nullptr;
-    return thread->_result;
-  }
+  });
 }
 
 /**
@@ -1189,56 +1151,13 @@ extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) {
  */
 vector_uchar GraphicsEngine::
 extract_shader_buffer_data(ShaderBuffer *buffer, GraphicsStateGuardian *gsg) {
-  ReMutexHolder holder(_lock);
-
-  string draw_name = gsg->get_threading_model().get_draw_name();
-  if (draw_name.empty()) {
-    // A single-threaded environment.  No problem.
+  return run_on_draw_thread([=] () {
     vector_uchar data;
     if (!gsg->extract_shader_buffer_data(buffer, data)) {
       data.clear();
     }
     return data;
-  }
-
-  // A multi-threaded environment.  We have to wait until the draw thread
-  // has finished its current task.
-  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();
-  }
-
-  // Temporarily set this so that it accesses data from the current thread.
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  int draw_pipeline_stage = thread->get_pipeline_stage();
-  thread->set_pipeline_stage(pipeline_stage);
-
-  // Now that the draw thread is idle, signal it to do the extraction task.
-  vector_uchar data;
-  thread->_gsg = gsg;
-  thread->_buffer = buffer;
-  thread->_buffer_result = &data;
-  thread->_thread_state = TS_do_extract_shader_buffer_data;
-  thread->_cv_mutex.release();
-  thread->_cv_start.notify();
-  thread->_cv_mutex.acquire();
-
-  // Wait for it to finish the extraction.
-  while (thread->_thread_state != TS_wait) {
-    thread->_cv_done.wait();
-  }
-
-  thread->set_pipeline_stage(draw_pipeline_stage);
-  thread->_gsg = nullptr;
-  thread->_buffer = nullptr;
-  thread->_buffer_result = nullptr;
-  if (!thread->_result) {
-    data.clear();
-  }
-  return data;
+  });
 }
 
 /**
@@ -1263,50 +1182,12 @@ dispatch_compute(const LVecBase3i &work_groups, const RenderState *state, Graphi
   nassertv(shader != nullptr);
   nassertv(gsg != nullptr);
 
-  ReMutexHolder holder(_lock);
-
-  string draw_name = gsg->get_threading_model().get_draw_name();
-  if (draw_name.empty()) {
-    // A single-threaded environment.  No problem.
+  run_on_draw_thread([=] () {
     gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename());
     gsg->set_state_and_transform(state, TransformState::make_identity());
     gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
     gsg->pop_group_marker();
-
-  } else {
-    // A multi-threaded environment.  We have to wait until the draw thread
-    // has finished its current task.
-    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();
-    }
-
-    // Temporarily set this so that it accesses data from the current thread.
-    int pipeline_stage = Thread::get_current_pipeline_stage();
-    int draw_pipeline_stage = thread->get_pipeline_stage();
-    thread->set_pipeline_stage(pipeline_stage);
-
-    // Now that the draw thread is idle, signal it to do the compute task.
-    thread->_gsg = gsg;
-    thread->_state = state;
-    thread->_work_groups = work_groups;
-    thread->_thread_state = TS_do_compute;
-    thread->_cv_mutex.release();
-    thread->_cv_start.notify();
-    thread->_cv_mutex.acquire();
-
-    // Wait for it to finish the compute task.
-    while (thread->_thread_state != TS_wait) {
-      thread->_cv_done.wait();
-    }
-
-    thread->set_pipeline_stage(draw_pipeline_stage);
-    thread->_gsg = nullptr;
-    thread->_state = nullptr;
-  }
+  });
 }
 
 /**
@@ -1342,43 +1223,6 @@ 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_mutex.release();
-  thread->_cv_start.notify();
-  thread->_cv_mutex.acquire();
-
-  // Wait for it to finish the extraction.
-  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.
  */
@@ -2867,31 +2711,9 @@ thread_main() {
       do_pending(_engine, current_thread);
       break;
 
-    case TS_do_compute:
-      nassertd(_gsg != nullptr && _state != nullptr) break;
-      {
-        const ShaderAttrib *sattr;
-        _state->get_attrib(sattr);
-        _gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename());
-        _gsg->set_state_and_transform(_state, TransformState::make_identity());
-        _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
-        _gsg->pop_group_marker();
-      }
-      break;
-
-    case TS_do_extract_texture_data:
-      nassertd(_gsg != nullptr && _texture != nullptr) break;
-      _result = _gsg->extract_texture_data(_texture);
-      break;
-
-    case TS_do_extract_shader_buffer_data:
-      nassertd(_gsg != nullptr && _texture != nullptr) break;
-      _result = _gsg->extract_shader_buffer_data(_buffer, *_buffer_result);
-      break;
-
-    case TS_do_screenshot:
-      nassertd(_region != nullptr) break;
-      _texture = _region->get_screenshot();
+    case TS_callback:
+      nassertd(_callback != nullptr) break;
+      _callback(this);
       break;
 
     case TS_terminate:
@@ -2916,3 +2738,39 @@ thread_main() {
     }
   }
 }
+
+/**
+ * Waits for this thread to become idle, then runs the given function on it.
+ */
+void GraphicsEngine::RenderThread::
+run_on_thread(Callback *callback, void *callback_data, void *return_data) {
+  MutexHolder cv_holder(_cv_mutex);
+
+  while (_thread_state != TS_wait) {
+    _cv_done.wait();
+  }
+
+  // Temporarily set this so that it accesses data from the current thread.
+  int pipeline_stage = Thread::get_current_pipeline_stage();
+  int thread_pipeline_stage = get_pipeline_stage();
+  set_pipeline_stage(pipeline_stage);
+
+  // Now that the draw thread is idle, signal it to run the callback.
+  _callback = callback;
+  _callback_data = callback_data;
+  _return_data = return_data;
+  _thread_state = TS_callback;
+  _cv_mutex.release();
+  _cv_start.notify();
+  _cv_mutex.acquire();
+
+  // Wait for it to finish the job.
+  while (_thread_state != TS_wait) {
+    _cv_done.wait();
+  }
+
+  set_pipeline_stage(thread_pipeline_stage);
+  _callback = nullptr;
+  _callback_data = nullptr;
+  _return_data = nullptr;
+}

+ 27 - 14
panda/src/display/graphicsEngine.h

@@ -35,6 +35,8 @@
 #include "renderState.h"
 #include "clockObject.h"
 
+#include <type_traits>
+
 class Pipeline;
 class DisplayRegion;
 class GraphicsPipe;
@@ -130,16 +132,17 @@ public:
     TS_do_flip,
     TS_do_release,
     TS_do_windows,
-    TS_do_compute,
-    TS_do_extract_texture_data,
-    TS_do_extract_shader_buffer_data,
-    TS_do_screenshot,
+    TS_callback,
     TS_terminate,
     TS_done
   };
 
   void texture_uploaded(Texture *tex);
-  PT(Texture) do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg);
+
+#ifndef CPPPARSER
+  template<class Callable>
+  INLINE auto run_on_draw_thread(Callable &&callable) -> decltype(callable());
+#endif
 
 public:
   static void do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
@@ -306,21 +309,31 @@ private:
     RenderThread(const std::string &name, GraphicsEngine *engine);
     virtual void thread_main();
 
+    typedef void Callback(RenderThread *thread);
+    void run_on_thread(Callback *callback,
+                       void *callback_data = nullptr,
+                       void *return_data = nullptr);
+
+#ifndef CPPPARSER
+    template<class Callable>
+    INLINE auto run_on_thread(Callable &&callable) ->
+      typename std::enable_if<!std::is_void<decltype(callable())>::value, decltype(callable())>::type;
+
+    template<class Callable>
+    INLINE auto run_on_thread(Callable &&callable) ->
+      typename std::enable_if<std::is_void<decltype(callable())>::value, decltype(callable())>::type;
+#endif
+
     GraphicsEngine *_engine;
     Mutex _cv_mutex;
     ConditionVar _cv_start;
     ConditionVar _cv_done;
     ThreadState _thread_state;
 
-    // These are stored for extract_texture_data and dispatch_compute.
-    GraphicsStateGuardian *_gsg;
-    PT(Texture) _texture;
-    ShaderBuffer *_buffer;
-    vector_uchar *_buffer_result;
-    const RenderState *_state;
-    DisplayRegion *_region;
-    LVecBase3i _work_groups;
-    bool _result;
+    // Used for TS_callback.
+    Callback *_callback;
+    void *_callback_data;
+    void *_return_data;
   };
 
   WindowRenderer *get_window_renderer(const std::string &name, int pipeline_stage);