diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 3b4974f287..56d9e3da1e 100644 --- a/panda/src/display/config_display.cxx +++ b/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 = 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 // (primarily useful for debugging). 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. 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) { return disp->begin(); diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index 4b1a557067..cd789ce9e9 100644 --- a/panda/src/display/config_display.h +++ b/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 compare_state_by_pointer; -extern const bool cull_sorting; extern const bool view_frustum_cull; 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_end(void); extern Config::ConfigTable::Symbol::iterator gsg_modules_begin(void); diff --git a/panda/src/display/graphicsEngine.I b/panda/src/display/graphicsEngine.I index 4f0a1010e6..1d538fbbb0 100644 --- a/panda/src/display/graphicsEngine.I +++ b/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(); + } +} diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 890c238e8f..8e89e39b0f 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -26,6 +26,8 @@ #include "clockObject.h" #include "pStatTimer.h" #include "pStatClient.h" +#include "mutexHolder.h" +#include "string_utils.h" #ifndef CPPPARSER PStatCollector GraphicsEngine::_cull_pcollector("Cull"); @@ -47,6 +49,32 @@ GraphicsEngine(Pipeline *pipeline) : if (_pipeline == (Pipeline *)NULL) { _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:: 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 { - 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, @@ -107,16 +146,191 @@ render_frame() { // only for special effects, like shaders, that require // a complete offscreen render pass before they can // 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:: render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr) { - if (cull_sorting) { + if (_cull_sorting) { cull_bin_draw(gsg, dr); } else { 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 // Access: Private @@ -191,6 +405,7 @@ cull_bin_draw() { DisplayRegion *dr = win->get_display_region(i); cull_bin_draw(win->get_gsg(), dr); } + win->end_frame(); } win->process_events(); @@ -387,3 +602,124 @@ setup_gsg(GraphicsStateGuardian *gsg, SceneSetup *scene_setup) { 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 + << ")**"; + } +} diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index 0246ca74ef..027cc653ec 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -23,6 +23,9 @@ #include "graphicsWindow.h" #include "sceneSetup.h" #include "pointerTo.h" +#include "thread.h" +#include "mutex.h" +#include "conditionVar.h" #include "pset.h" #include "pStatCollector.h" @@ -46,6 +49,7 @@ class DisplayRegion; class EXPCL_PANDA GraphicsEngine : public Namable { PUBLISHED: GraphicsEngine(Pipeline *pipeline = NULL); + ~GraphicsEngine(); void add_window(GraphicsWindow *window); bool remove_window(GraphicsWindow *window); @@ -53,7 +57,24 @@ PUBLISHED: void render_frame(); 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: + INLINE void start_cull(); void cull_and_draw_together(); void cull_and_draw_together(GraphicsStateGuardian *gsg, DisplayRegion *dr); @@ -69,15 +90,56 @@ private: 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; typedef pset Windows; Windows _windows; + ThreadingModel _threading_model; + bool _cull_sorting; + + PT(CullThread) _cull_thread; + PT(DrawThread) _draw_thread; + static PStatCollector _cull_pcollector; static PStatCollector _draw_pcollector; + + friend class CullThread; }; +ostream & +operator << (ostream &out, GraphicsEngine::ThreadingModel threading_model); + #include "graphicsEngine.I" #endif