Browse Source

first pass (broken) of multi-threaded rendering

David Rose 23 năm trước cách đây
mục cha
commit
a8d12a79b5

+ 11 - 6
panda/src/display/config_display.cxx

@@ -58,12 +58,6 @@ const bool pipe_spec_is_remote = config_display.Defined("pipe-machine")
 const bool compare_state_by_pointer =
 const bool compare_state_by_pointer =
 config_display.GetBool("compare-state-by-pointer", true);
 config_display.GetBool("compare-state-by-pointer", true);
 
 
-// This is normally true to enable the cull traversal to perform
-// state-sorting and alpha-sorting.  Turn this false to disable these
-// features and likely improve cull performance at the expense of draw
-// (and at the expense of correct alpha).
-const bool cull_sorting = config_display.GetBool("cull-sorting", true);
-
 // This is normally true; set it false to disable view-frustum culling
 // This is normally true; set it false to disable view-frustum culling
 // (primarily useful for debugging).
 // (primarily useful for debugging).
 const bool view_frustum_cull = config_display.GetBool("view-frustum-cull", true);
 const bool view_frustum_cull = config_display.GetBool("view-frustum-cull", true);
@@ -73,6 +67,17 @@ const bool view_frustum_cull = config_display.GetBool("view-frustum-cull", true)
 // of per-frame overhead to count these things up.
 // of per-frame overhead to count these things up.
 const bool pstats_unused_states = config_display.GetBool("pstats-unused-states", false);
 const bool pstats_unused_states = config_display.GetBool("pstats-unused-states", false);
 
 
+// This is the default threading model to use when the GraphicsEngine
+// is created, e.g. app-cull-draw or appculldraw or some such.  (See
+// GraphicsEngine::set_threading_model()).
+
+// Warning!  The code that uses this is currently experimental and
+// incomplete, and will almost certainly crash!  Do not set
+// threading-model to anything other than its default of a
+// single-threaded model unless you are developing Panda's threading
+// system!
+const string threading_model = config_display.GetString("threading-model", "appculldraw");
+
 
 
 Config::ConfigTable::Symbol::iterator pipe_modules_begin(void) {
 Config::ConfigTable::Symbol::iterator pipe_modules_begin(void) {
   return disp->begin();
   return disp->begin();

+ 2 - 1
panda/src/display/config_display.h

@@ -36,10 +36,11 @@ extern const bool pipe_spec_is_file;
 extern const bool pipe_spec_is_remote;
 extern const bool pipe_spec_is_remote;
 
 
 extern const bool compare_state_by_pointer;
 extern const bool compare_state_by_pointer;
-extern const bool cull_sorting;
 extern const bool view_frustum_cull;
 extern const bool view_frustum_cull;
 extern const bool pstats_unused_states;
 extern const bool pstats_unused_states;
 
 
+extern const string threading_model;
+
 extern Config::ConfigTable::Symbol::iterator pipe_modules_begin(void);
 extern Config::ConfigTable::Symbol::iterator pipe_modules_begin(void);
 extern Config::ConfigTable::Symbol::iterator pipe_modules_end(void);
 extern Config::ConfigTable::Symbol::iterator pipe_modules_end(void);
 extern Config::ConfigTable::Symbol::iterator gsg_modules_begin(void);
 extern Config::ConfigTable::Symbol::iterator gsg_modules_begin(void);

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

@@ -16,3 +16,30 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::get_threading_model
+//       Access: Published
+//  Description: Returns the current threading model in use; see
+//               set_threading_model().
+////////////////////////////////////////////////////////////////////
+INLINE GraphicsEngine::ThreadingModel GraphicsEngine::
+get_threading_model() const {
+  return _threading_model;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::start_cull
+//       Access: Private
+//  Description: Starts the cull stage for the current frame.  This
+//               may or may not execute within a sub-thread, according
+//               to the current threading model.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsEngine::
+start_cull() {
+  if (_cull_sorting) {
+    cull_bin_draw();
+  } else {
+    cull_and_draw_together();
+  }
+}

+ 340 - 4
panda/src/display/graphicsEngine.cxx

@@ -26,6 +26,8 @@
 #include "clockObject.h"
 #include "clockObject.h"
 #include "pStatTimer.h"
 #include "pStatTimer.h"
 #include "pStatClient.h"
 #include "pStatClient.h"
+#include "mutexHolder.h"
+#include "string_utils.h"
 
 
 #ifndef CPPPARSER
 #ifndef CPPPARSER
 PStatCollector GraphicsEngine::_cull_pcollector("Cull");
 PStatCollector GraphicsEngine::_cull_pcollector("Cull");
@@ -47,6 +49,32 @@ GraphicsEngine(Pipeline *pipeline) :
   if (_pipeline == (Pipeline *)NULL) {
   if (_pipeline == (Pipeline *)NULL) {
     _pipeline = Pipeline::get_render_pipeline();
     _pipeline = Pipeline::get_render_pipeline();
   }
   }
+
+  ThreadingModel tm = string_threading_model(threading_model);
+  if (tm == TM_invalid) {
+    display_cat.warning()
+      << "Invalid threading model: " << threading_model << "\n";
+    set_threading_model(TM_appculldraw);
+  } else {
+    set_threading_model(tm);
+  }
+
+  if (tm != TM_appculldraw) {
+    display_cat.info()
+      << "Using threading model " << get_threading_model() << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::Destructor
+//       Access: Published
+//  Description: Gracefully cleans up the graphics engine and its
+//               related threads.  Does not delete the associated
+//               windows.
+////////////////////////////////////////////////////////////////////
+GraphicsEngine::
+~GraphicsEngine() {
+  terminate_threads();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -87,10 +115,21 @@ remove_window(GraphicsWindow *window) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
 void GraphicsEngine::
 render_frame() {
 render_frame() {
-  if (cull_sorting) {
-    cull_bin_draw();
+  if (_cull_thread == (Thread *)NULL) {
+    // Nonthreaded cull: perform cull within the app thread.  This
+    // corresponds to either TM_appculldraw, TM_appcull_draw, or
+    // TM_appcdraw.
+    start_cull();
+
   } else {
   } else {
-    cull_and_draw_together();
+    // Threaded cull: perform cull within its own thread.  Here we
+    // simply signal the thread and then let it go about its business
+    // while we return.
+    MutexHolder holder(_cull_thread->_cv_mutex);
+    if (_cull_thread->_thread_state == TS_wait) {
+      _cull_thread->_thread_state = TS_do_frame;
+      _cull_thread->_cv.signal();
+    }
   }
   }
 
 
   // **** This doesn't belong here; it really belongs in the Pipeline,
   // **** This doesn't belong here; it really belongs in the Pipeline,
@@ -107,16 +146,191 @@ render_frame() {
 //               only for special effects, like shaders, that require
 //               only for special effects, like shaders, that require
 //               a complete offscreen render pass before they can
 //               a complete offscreen render pass before they can
 //               complete.
 //               complete.
+//
+//               This always executes completely within the calling
+//               thread, regardless of the threading model in use.
+//               Thus, it may be an error to call this from the app
+//               thread if the threading model is not TM_appculldraw;
+//               similarly, it is an error to call it from cull if the
+//               threading model is TM_appcull_draw or
+//               TM_app_cull_draw.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
 void GraphicsEngine::
 render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
 render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
-  if (cull_sorting) {
+  if (_cull_sorting) {
     cull_bin_draw(gsg, dr);
     cull_bin_draw(gsg, dr);
   } else {
   } else {
     cull_and_draw_together(gsg, dr);
     cull_and_draw_together(gsg, dr);
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::set_threading_model
+//       Access: Published
+//  Description: Changes the way the app, cull, and draw steps are
+//               divided among threads.  The allowable settings are:
+//
+//                TM_appculldraw: single-threaded.  All steps are
+//                  performed by the calling thread; render_frame()
+//                  does not return until the frame has been fully
+//                  rendered.
+//
+//                TM_appcull_draw: app and cull are performed
+//                  together, while draw is performed in a separate
+//                  thread.  render_frame() will return after cull has
+//                  been completed; draw will have started in the
+//                  sub-thread.
+//
+//                TM_app_culldraw: app is separate, while cull and
+//                  draw are performed in sequence within a separate
+//                  thread.  render_frame() will return almost
+//                  immediately (after waiting for cull to start in
+//                  the sub-thread).
+//
+//                TM_app_cull_draw: all stages of the pipeline are in
+//                  separate threads.  render_frame() returns almost
+//                  immediately, after waiting for cull to start in
+//                  its sub-thread; when cull finishes, it passes the
+//                  frame off to a separate thread for draw and then
+//                  begins working on culling the next frame.
+//
+//                TM_appcdraw: single-threaded, like TM_appculldraw,
+//                  but cull and draw are performed simultaneously,
+//                  rather than sequentially.  This means that the
+//                  scene will always be rendered in scene graph
+//                  order; binning, state-sorting and back-to-front
+//                  sorting cannot be achieved.
+//
+//                TM_app_cdraw: similar to TM_appcdraw, but the
+//                  cull&draw step is performed in a separate thread.
+////////////////////////////////////////////////////////////////////
+void GraphicsEngine::
+set_threading_model(ThreadingModel threading_model) {
+  bool spawn_cull_thread;
+  bool spawn_draw_thread;
+  bool cull_sorting;
+
+  switch (threading_model) {
+  case TM_appculldraw:
+    spawn_cull_thread = false;
+    spawn_draw_thread = false;
+    cull_sorting = true;
+    break;
+
+  case TM_appcull_draw:
+    spawn_cull_thread = false;
+    spawn_draw_thread = true;
+    cull_sorting = true;
+    break;
+
+  case TM_app_culldraw:
+    spawn_cull_thread = true;
+    spawn_draw_thread = false;
+    cull_sorting = true;
+    break;
+
+  case TM_app_cull_draw:
+    spawn_cull_thread = true;
+    spawn_draw_thread = true;
+    cull_sorting = true;
+    break;
+
+  case TM_appcdraw:
+    spawn_cull_thread = false;
+    spawn_draw_thread = false;
+    cull_sorting = false;
+    break;
+
+  case TM_app_cdraw:
+    spawn_cull_thread = true;
+    spawn_draw_thread = false;
+    cull_sorting = false;
+    break;
+
+  default:
+    display_cat.error()
+      << "Invalid threading model: " << (int)threading_model << "\n";
+    return;
+  }
+
+  if ((spawn_cull_thread || spawn_draw_thread) && 
+      !Thread::is_threading_supported()) {
+    display_cat.warning()
+      << "Threading model " << threading_model
+      << " requested but threading not supported.\n";
+    threading_model = (cull_sorting) ? TM_appculldraw : TM_appcdraw;
+    spawn_cull_thread = false;
+    spawn_draw_thread = false;
+  }
+
+  if (_threading_model != threading_model || _cull_sorting != cull_sorting) {
+    terminate_threads();
+
+    _threading_model = threading_model;
+    _cull_sorting = cull_sorting;
+
+    if (spawn_cull_thread) {
+      const char *thread_name;
+      if (spawn_draw_thread) {
+        thread_name = "cull";
+      } else if (cull_sorting) {
+        thread_name = "culldraw";
+      } else {
+        thread_name = "cdraw";
+      }
+      _cull_thread = new CullThread(thread_name, this);
+      MutexHolder holder(_cull_thread->_cv_mutex);
+      if (!_cull_thread->start(TP_normal, false, true)) {
+        display_cat.error()
+          << "Unable to start cull thread.\n";
+        _cull_thread = (CullThread *)NULL;
+      }
+    }
+
+    if (spawn_draw_thread) {
+      _draw_thread = new DrawThread("draw", this);
+      MutexHolder holder(_draw_thread->_cv_mutex);
+      if (!_draw_thread->start(TP_normal, true, true)) {
+        display_cat.error()
+          << "Unable to start draw thread.\n";
+        _draw_thread = (DrawThread *)NULL;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::string_threading_model
+//       Access: Public, Static
+//  Description: Returns the ThreadingModel value corresponding to the
+//               indicated string, or TM_invalid if the threading
+//               model is invalid.
+////////////////////////////////////////////////////////////////////
+GraphicsEngine::ThreadingModel GraphicsEngine::
+string_threading_model(const string &string) {
+  if (cmp_nocase_uh(string, "appculldraw") == 0) {
+    return TM_appculldraw;
+
+  } else if (cmp_nocase_uh(string, "appcull_draw") == 0) {
+    return TM_appcull_draw;
+
+  } else if (cmp_nocase_uh(string, "app_culldraw") == 0) {
+    return TM_app_culldraw;
+
+  } else if (cmp_nocase_uh(string, "app_cull_draw") == 0) {
+    return TM_app_cull_draw;
+
+  } else if (cmp_nocase_uh(string, "appcdraw") == 0) {
+    return TM_appcdraw;
+
+  } else if (cmp_nocase_uh(string, "app_cdraw") == 0) {
+    return TM_app_cdraw;
+
+  } else {
+    return TM_invalid;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsEngine::cull_and_draw_together
 //     Function: GraphicsEngine::cull_and_draw_together
 //       Access: Private
 //       Access: Private
@@ -191,6 +405,7 @@ cull_bin_draw() {
         DisplayRegion *dr = win->get_display_region(i);
         DisplayRegion *dr = win->get_display_region(i);
         cull_bin_draw(win->get_gsg(), dr);
         cull_bin_draw(win->get_gsg(), dr);
       }
       }
+
       win->end_frame();
       win->end_frame();
     }
     }
     win->process_events();
     win->process_events();
@@ -387,3 +602,124 @@ setup_gsg(GraphicsStateGuardian *gsg, SceneSetup *scene_setup) {
 
 
   return true;
   return true;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::terminate_threads
+//       Access: Private
+//  Description: Signals our child threads to terminate and waits for
+//               them to clean up.
+////////////////////////////////////////////////////////////////////
+void GraphicsEngine::
+terminate_threads() {
+  if (_cull_thread != (Thread *)NULL) {
+    MutexHolder holder(_cull_thread->_cv_mutex);
+    _cull_thread->_thread_state = TS_terminate;
+    _cull_thread->_cv.signal();
+  }
+  if (_draw_thread != (Thread *)NULL) {
+    MutexHolder holder(_draw_thread->_cv_mutex);
+    _draw_thread->_thread_state = TS_terminate;
+    _draw_thread->_cv.signal();
+  }
+
+  if (_cull_thread != (Thread *)NULL) {
+    _cull_thread->join();
+    _cull_thread = (CullThread *)NULL;
+  }
+  if (_draw_thread != (Thread *)NULL) {
+    _draw_thread->join();
+    _draw_thread = (DrawThread *)NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::CullThread::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+GraphicsEngine::CullThread::
+CullThread(const string &name, GraphicsEngine *engine) : 
+  Thread(name),
+  _engine(engine),
+  _cv(_cv_mutex)
+{
+  _thread_state = TS_wait;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::CullThread::thread_main
+//       Access: Public, Virtual
+//  Description: Runs the cull thread.
+////////////////////////////////////////////////////////////////////
+void GraphicsEngine::CullThread::
+thread_main() {
+  MutexHolder holder(_cv_mutex);
+  while (_thread_state != TS_terminate) {
+    _cv.wait();
+    if (_thread_state == TS_do_frame) {
+      _engine->start_cull();
+      _thread_state = TS_wait;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::DrawThread::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+GraphicsEngine::DrawThread::
+DrawThread(const string &name, GraphicsEngine *engine) : 
+  Thread(name),
+  _engine(engine),
+  _cv(_cv_mutex)
+{
+  _thread_state = TS_wait;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::DrawThread::thread_main
+//       Access: Public, Virtual
+//  Description: Runs the draw thread.
+////////////////////////////////////////////////////////////////////
+void GraphicsEngine::DrawThread::
+thread_main() {
+  MutexHolder holder(_cv_mutex);
+  while (_thread_state != TS_terminate) {
+    _cv.wait();
+    if (_thread_state == TS_do_frame) {
+      //      _engine->start_draw();
+      _thread_state = TS_wait;
+    }
+  }
+}
+
+ostream &
+operator << (ostream &out, GraphicsEngine::ThreadingModel threading_model) {
+  switch (threading_model) {
+  case GraphicsEngine::TM_invalid:
+    return out << "invalid";
+    
+  case GraphicsEngine::TM_appculldraw:
+    return out << "appculldraw";
+
+  case GraphicsEngine::TM_appcull_draw:
+    return out << "appcull_draw";
+
+  case GraphicsEngine::TM_app_culldraw:
+    return out << "app_culldraw";
+
+  case GraphicsEngine::TM_app_cull_draw:
+    return out << "app_cull_draw";
+
+  case GraphicsEngine::TM_appcdraw:
+    return out << "appcdraw";
+
+  case GraphicsEngine::TM_app_cdraw:
+    return out << "app_cdraw";
+  
+  default:
+    return out << "**invalid threading model (" << (int)threading_model
+               << ")**";
+  }
+}

+ 62 - 0
panda/src/display/graphicsEngine.h

@@ -23,6 +23,9 @@
 #include "graphicsWindow.h"
 #include "graphicsWindow.h"
 #include "sceneSetup.h"
 #include "sceneSetup.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
+#include "thread.h"
+#include "mutex.h"
+#include "conditionVar.h"
 #include "pset.h"
 #include "pset.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
 
 
@@ -46,6 +49,7 @@ class DisplayRegion;
 class EXPCL_PANDA GraphicsEngine : public Namable {
 class EXPCL_PANDA GraphicsEngine : public Namable {
 PUBLISHED:
 PUBLISHED:
   GraphicsEngine(Pipeline *pipeline = NULL);
   GraphicsEngine(Pipeline *pipeline = NULL);
+  ~GraphicsEngine();
 
 
   void add_window(GraphicsWindow *window);
   void add_window(GraphicsWindow *window);
   bool remove_window(GraphicsWindow *window);
   bool remove_window(GraphicsWindow *window);
@@ -53,7 +57,24 @@ PUBLISHED:
   void render_frame();
   void render_frame();
   void render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr);
   void render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr);
 
 
+  enum ThreadingModel {
+    TM_invalid,
+    TM_appculldraw,
+    TM_appcull_draw,
+    TM_app_culldraw,
+    TM_app_cull_draw,
+    TM_appcdraw,
+    TM_app_cdraw,
+  };
+
+  void set_threading_model(ThreadingModel threading_model);
+  INLINE ThreadingModel get_threading_model() const;
+
+public:
+  static ThreadingModel string_threading_model(const string &string);
+  
 private:
 private:
+  INLINE void start_cull();
   void cull_and_draw_together();
   void cull_and_draw_together();
   void cull_and_draw_together(GraphicsStateGuardian *gsg, DisplayRegion *dr);
   void cull_and_draw_together(GraphicsStateGuardian *gsg, DisplayRegion *dr);
 
 
@@ -69,15 +90,56 @@ private:
 
 
   bool setup_gsg(GraphicsStateGuardian *gsg, SceneSetup *scene_setup);
   bool setup_gsg(GraphicsStateGuardian *gsg, SceneSetup *scene_setup);
 
 
+  void terminate_threads();
+
+  enum ThreadState {
+    TS_wait,
+    TS_do_frame,
+    TS_terminate
+  };
+
+  class CullThread : public Thread {
+  public:
+    CullThread(const string &name, GraphicsEngine *engine);
+    virtual void thread_main();
+
+    GraphicsEngine *_engine;
+    Mutex _cv_mutex;
+    ConditionVar _cv;
+    ThreadState _thread_state;
+  };
+
+  class DrawThread : public Thread {
+  public:
+    DrawThread(const string &name, GraphicsEngine *engine);
+    virtual void thread_main();
+
+    GraphicsEngine *_engine;
+    Mutex _cv_mutex;
+    ConditionVar _cv;
+    ThreadState _thread_state;
+  };
+
   Pipeline *_pipeline;
   Pipeline *_pipeline;
 
 
   typedef pset<PT(GraphicsWindow)> Windows;
   typedef pset<PT(GraphicsWindow)> Windows;
   Windows _windows;
   Windows _windows;
 
 
+  ThreadingModel _threading_model;
+  bool _cull_sorting;
+
+  PT(CullThread) _cull_thread;
+  PT(DrawThread) _draw_thread;
+
   static PStatCollector _cull_pcollector;
   static PStatCollector _cull_pcollector;
   static PStatCollector _draw_pcollector;
   static PStatCollector _draw_pcollector;
+
+  friend class CullThread;
 };
 };
 
 
+ostream &
+operator << (ostream &out, GraphicsEngine::ThreadingModel threading_model);
+
 #include "graphicsEngine.I"
 #include "graphicsEngine.I"
 
 
 #endif
 #endif