Browse Source

better pipelining

David Rose 20 years ago
parent
commit
c75b55eb98
39 changed files with 425 additions and 126 deletions
  1. 92 56
      panda/src/display/graphicsEngine.cxx
  2. 1 0
      panda/src/display/graphicsEngine.h
  3. 12 0
      panda/src/express/thread.I
  4. 1 0
      panda/src/express/thread.h
  5. 3 0
      panda/src/gobj/boundedObject.h
  6. 3 0
      panda/src/gobj/geom.h
  7. 3 0
      panda/src/gobj/geomPrimitive.h
  8. 3 0
      panda/src/gobj/geomVertexArrayData.h
  9. 12 0
      panda/src/gobj/geomVertexData.I
  10. 30 32
      panda/src/gobj/geomVertexData.cxx
  11. 8 4
      panda/src/gobj/geomVertexData.h
  12. 3 0
      panda/src/gobj/sliderTable.h
  13. 3 0
      panda/src/gobj/transformBlendTable.h
  14. 3 0
      panda/src/gobj/transformTable.h
  15. 3 0
      panda/src/gobj/userVertexSlider.h
  16. 3 0
      panda/src/gobj/userVertexTransform.h
  17. 3 0
      panda/src/gobj/vertexSlider.h
  18. 3 0
      panda/src/gobj/vertexTransform.h
  19. 3 0
      panda/src/parametrics/ropeNode.h
  20. 3 0
      panda/src/parametrics/sheetNode.h
  21. 3 0
      panda/src/pgraph/directionalLight.h
  22. 3 0
      panda/src/pgraph/geomNode.h
  23. 3 0
      panda/src/pgraph/light.h
  24. 3 0
      panda/src/pgraph/lodNode.h
  25. 3 0
      panda/src/pgraph/nodePathComponent.h
  26. 3 0
      panda/src/pgraph/pandaNode.h
  27. 3 0
      panda/src/pgraph/planeNode.h
  28. 3 0
      panda/src/pgraph/pointLight.h
  29. 3 0
      panda/src/pgraph/spotlight.h
  30. 3 0
      panda/src/pgraph/switchNode.h
  31. 3 0
      panda/src/putil/animInterface.h
  32. 22 0
      panda/src/putil/cycleData.cxx
  33. 10 1
      panda/src/putil/cycleData.h
  34. 11 0
      panda/src/putil/pipeline.I
  35. 24 4
      panda/src/putil/pipeline.cxx
  36. 4 0
      panda/src/putil/pipeline.h
  37. 40 22
      panda/src/putil/pipelineCyclerTrueImpl.I
  38. 82 6
      panda/src/putil/pipelineCyclerTrueImpl.cxx
  39. 4 1
      panda/src/putil/pipelineCyclerTrueImpl.h

+ 92 - 56
panda/src/display/graphicsEngine.cxx

@@ -46,6 +46,7 @@
 #endif
 
 PStatCollector GraphicsEngine::_wait_pcollector("Wait");
+PStatCollector GraphicsEngine::_cycle_pcollector("App:Cycle");
 PStatCollector GraphicsEngine::_app_pcollector("App");
 PStatCollector GraphicsEngine::_yield_pcollector("App:Yield");
 PStatCollector GraphicsEngine::_cull_pcollector("Cull");
@@ -487,12 +488,35 @@ render_frame() {
     GraphicsOutput *win = (*wi);
     if (win->get_delete_flag()) {
       do_remove_window(win);
-
+      
     } else {
       new_windows.push_back(win);
+      
+      // Let's calculate each scene's bounding volume here in App,
+      // before we cycle the pipeline.  The cull traversal will
+      // calculate it anyway, but if we calculate it in App first
+      // before it gets calculated in the Cull thread, it will be more
+      // likely to stick for subsequent frames, so we won't have to
+      // recompute it each frame.
+      int num_drs = win->get_num_active_display_regions();
+      for (int i = 0; i < num_drs; ++i) {
+        DisplayRegion *dr = win->get_active_display_region(i);
+        if (dr != (DisplayRegion *)NULL) {
+          NodePath camera_np = dr->get_camera();
+          if (!camera_np.is_empty()) {
+            Camera *camera = DCAST(Camera, camera_np.node());
+            NodePath scene = camera->get_scene();
+            if (scene.is_empty()) {
+              scene = camera_np.get_top();
+            }
+            if (!scene.is_empty()) {
+              scene.get_bounds();
+            }
+          }
+        }
+      }
     }
   }
-  new_windows.swap(_windows);
 
   // Now it's time to do any drawing from the main frame--after all of
   // the App code has executed, but before we begin the next frame.
@@ -500,13 +524,13 @@ render_frame() {
   
   // Grab each thread's mutex again after all windows have flipped,
   // and wait for the thread to finish.
-  Threads::const_iterator ti;
-  for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-    RenderThread *thread = (*ti).second;
-    thread->_cv_mutex.lock();
-
-    if (thread->_thread_state != TS_wait) {
-      PStatTimer timer(_wait_pcollector);
+  {
+    PStatTimer timer(_wait_pcollector);
+    Threads::const_iterator ti;
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      RenderThread *thread = (*ti).second;
+      thread->_cv_mutex.lock();
+      
       while (thread->_thread_state != TS_wait) {
         thread->_cv_done.wait();
       }
@@ -514,7 +538,10 @@ render_frame() {
   }
 
   // Now cycle the pipeline and officially begin the next frame.
-  _pipeline->cycle();
+  {
+    PStatTimer timer(_cycle_pcollector);
+    _pipeline->cycle();
+  }
   ClockObject *global_clock = ClockObject::get_global_clock();
   global_clock->tick();
   if (global_clock->check_errors()) {
@@ -539,6 +566,7 @@ render_frame() {
   }
 
   // Now signal all of our threads to begin their next frame.
+  Threads::const_iterator ti;
   for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
     RenderThread *thread = (*ti).second;
     if (thread->_thread_state == TS_wait) {
@@ -589,39 +617,36 @@ open_windows() {
 
   _app.do_windows(this);
 
-  Threads::const_iterator ti;
-  for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-    RenderThread *thread = (*ti).second;
-    thread->_cv_mutex.lock();
-
-    if (thread->_thread_state != TS_wait) {
-      PStatTimer timer(_wait_pcollector);
+  {
+    PStatTimer timer(_wait_pcollector);
+    Threads::const_iterator ti;
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      RenderThread *thread = (*ti).second;
+      thread->_cv_mutex.lock();
+      
       while (thread->_thread_state != TS_wait) {
         thread->_cv_done.wait();
       }
+      
+      thread->_thread_state = TS_do_windows;
+      thread->_cv_start.signal();
+      thread->_cv_mutex.release();
     }
 
-    thread->_thread_state = TS_do_windows;
-    thread->_cv_start.signal();
-    thread->_cv_mutex.release();
-  }
-
-  // We do it twice, to allow both cull and draw to process the
-  // window.
-  for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-    RenderThread *thread = (*ti).second;
-    thread->_cv_mutex.lock();
-
-    if (thread->_thread_state != TS_wait) {
-      PStatTimer timer(_wait_pcollector);
+    // We do it twice, to allow both cull and draw to process the
+    // window.
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      RenderThread *thread = (*ti).second;
+      thread->_cv_mutex.lock();
+      
       while (thread->_thread_state != TS_wait) {
         thread->_cv_done.wait();
       }
+      
+      thread->_thread_state = TS_do_windows;
+      thread->_cv_start.signal();
+      thread->_cv_mutex.release();
     }
-
-    thread->_thread_state = TS_do_windows;
-    thread->_cv_start.signal();
-    thread->_cv_mutex.release();
   }
 }
 
@@ -1008,13 +1033,13 @@ do_flip_frame() {
   // First, wait for all the threads to finish their current frame, if
   // necessary.  Grabbing the mutex (and waiting for TS_wait) should
   // achieve that.
-  Threads::const_iterator ti;
-  for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-    RenderThread *thread = (*ti).second;
-    thread->_cv_mutex.lock();
-
-    if (thread->_thread_state != TS_wait) {
-      PStatTimer timer(_wait_pcollector);
+  {
+    PStatTimer timer(_wait_pcollector);
+    Threads::const_iterator ti;
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      RenderThread *thread = (*ti).second;
+      thread->_cv_mutex.lock();
+      
       while (thread->_thread_state != TS_wait) {
         thread->_cv_done.wait();
       }
@@ -1023,12 +1048,16 @@ do_flip_frame() {
   
   // Now signal all of our threads to flip the windows.
   _app.do_flip(this);
-  for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
-    RenderThread *thread = (*ti).second;
-    nassertv(thread->_thread_state == TS_wait);
-    thread->_thread_state = TS_do_flip;
-    thread->_cv_start.signal();
-    thread->_cv_mutex.release();
+
+  {
+    Threads::const_iterator ti;
+    for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
+      RenderThread *thread = (*ti).second;
+      nassertv(thread->_thread_state == TS_wait);
+      thread->_thread_state = TS_do_flip;
+      thread->_cv_start.signal();
+      thread->_cv_mutex.release();
+    }
   }
 
   _flip_state = FS_flip;
@@ -1156,6 +1185,9 @@ do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
       trav.set_view_frustum(local_frustum);
     }
   }
+
+  static PStatCollector traverse("Cull:Traverse");
+  PStatTimer timer2(traverse);
   
   trav.traverse(scene_setup->get_scene_root(), get_portal_cull());
 }
@@ -1326,6 +1358,10 @@ do_resort_windows() {
 void GraphicsEngine::
 terminate_threads() {
   MutexHolder holder(_lock);
+
+  // We spend almost our entire time in this method just waiting for
+  // threads.  Time it appropriately.
+  PStatTimer timer(_wait_pcollector);
   
   // First, wait for all the threads to finish their current frame.
   // Grabbing the mutex should achieve that.
@@ -1334,26 +1370,26 @@ terminate_threads() {
     RenderThread *thread = (*ti).second;
     thread->_cv_mutex.lock();
   }
-
+  
   // Now tell them to release their windows' graphics contexts.
   for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
     RenderThread *thread = (*ti).second;
-
-    if (thread->_thread_state != TS_wait) {
-      PStatTimer timer(_wait_pcollector);
-      while (thread->_thread_state != TS_wait) {
-        thread->_cv_done.wait();
-      }
+    
+    while (thread->_thread_state != TS_wait) {
+      thread->_cv_done.wait();
     }
     thread->_thread_state = TS_do_release;
     thread->_cv_start.signal();
     thread->_cv_mutex.release();
   }
-
+  
   // Grab the mutex again to wait for the above to complete.
   for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
     RenderThread *thread = (*ti).second;
     thread->_cv_mutex.lock();
+    while (thread->_thread_state != TS_wait) {
+      thread->_cv_done.wait();
+    }
   }
 
   // Now tell them to close their windows and terminate.
@@ -1416,7 +1452,7 @@ get_window_renderer(const string &name) {
 
   PT(RenderThread) thread = new RenderThread(name, this);
   thread->set_pipeline_stage(1);
-  Pipeline::get_render_pipeline()->set_num_stages(2);
+  _pipeline->set_min_stages(2);
 
   thread->start(TP_normal, true, true);
   _threads[name] = thread;

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

@@ -246,6 +246,7 @@ private:
   Mutex _lock;
 
   static PStatCollector _wait_pcollector;
+  static PStatCollector _cycle_pcollector;
   static PStatCollector _app_pcollector;
   static PStatCollector _yield_pcollector;
   static PStatCollector _cull_pcollector;

+ 12 - 0
panda/src/express/thread.I

@@ -150,6 +150,18 @@ set_pipeline_stage(int pipeline_stage) {
   _pipeline_stage = pipeline_stage;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Thread::set_min_pipeline_stage
+//       Access: Published
+//  Description: Sets this thread's pipeline stage number to at least
+//               the indicated value, unless it is already larger.
+//               See set_pipeline_stage().
+////////////////////////////////////////////////////////////////////
+INLINE void Thread::
+set_min_pipeline_stage(int min_pipeline_stage) {
+  _pipeline_stage = max(_pipeline_stage, min_pipeline_stage);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Thread::get_main_thread
 //       Access: Published, Static

+ 1 - 0
panda/src/express/thread.h

@@ -63,6 +63,7 @@ PUBLISHED:
 
   INLINE int get_pipeline_stage() const;
   INLINE void set_pipeline_stage(int pipeline_stage);
+  INLINE void set_min_pipeline_stage(int min_pipeline_stage);
 
   INLINE static Thread *get_main_thread();
   INLINE static Thread *get_external_thread();

+ 3 - 0
panda/src/gobj/boundedObject.h

@@ -82,6 +82,9 @@ private:
     INLINE void operator = (const CData &copy);
 
     virtual CycleData *make_copy() const;
+    virtual TypeHandle get_parent_type() const {
+      return BoundedObject::get_class_type();
+    }
 
     int _flags;
     BoundingVolumeType _bound_type;

+ 3 - 0
panda/src/gobj/geom.h

@@ -177,6 +177,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return Geom::get_class_type();
+    }
 
     PT(GeomVertexData) _data;
     Primitives _primitives;

+ 3 - 0
panda/src/gobj/geomPrimitive.h

@@ -209,6 +209,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return GeomPrimitive::get_class_type();
+    }
 
     ShadeModel _shade_model;
     int _first_vertex;

+ 3 - 0
panda/src/gobj/geomVertexArrayData.h

@@ -123,6 +123,9 @@ private:
                                 void *extra_data) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager,
                         void *extra_data);
+    virtual TypeHandle get_parent_type() const {
+      return GeomVertexArrayData::get_class_type();
+    }
 
     UsageHint _usage_hint;
     PTA_uchar _data;

+ 12 - 0
panda/src/gobj/geomVertexData.I

@@ -234,6 +234,18 @@ get_modified() const {
   return cdata->_modified;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::clear_cache
+//       Access: Published
+//  Description: Removes all of the previously-cached results of
+//               convert_to().
+////////////////////////////////////////////////////////////////////
+INLINE void GeomVertexData::
+clear_cache() {
+  CDWriter cdata(_cycler);
+  do_clear_cache(cdata);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::has_vertex
 //       Access: Public

+ 30 - 32
panda/src/gobj/geomVertexData.cxx

@@ -152,7 +152,7 @@ operator = (const GeomVertexData &copy) {
   _morphs_pcollector = copy._morphs_pcollector;
 
   CDWriter cdata(_cycler);
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -218,7 +218,7 @@ set_usage_hint(GeomVertexData::UsageHint usage_hint) {
     }
     (*ai)->set_usage_hint(usage_hint);
   }
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -265,7 +265,7 @@ clear_rows() {
     }
     (*ai)->clear_rows();
   }
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices.clear();
 }
@@ -291,7 +291,7 @@ modify_array(int i) {
   if (cdata->_arrays[i]->get_ref_count() > 1) {
     cdata->_arrays[i] = new GeomVertexArrayData(*cdata->_arrays[i]);
   }
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 
@@ -311,7 +311,7 @@ set_array(int i, const GeomVertexArrayData *array) {
   CDWriter cdata(_cycler);
   nassertv(i >= 0 && i < (int)cdata->_arrays.size());
   cdata->_arrays[i] = (GeomVertexArrayData *)array;
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -331,7 +331,7 @@ set_transform_table(const TransformTable *table) {
 
   CDWriter cdata(_cycler);
   cdata->_transform_table = (TransformTable *)table;
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -354,7 +354,7 @@ modify_transform_blend_table() {
   if (cdata->_transform_blend_table->get_ref_count() > 1) {
     cdata->_transform_blend_table = new TransformBlendTable(*cdata->_transform_blend_table);
   }
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 
@@ -374,7 +374,7 @@ void GeomVertexData::
 set_transform_blend_table(const TransformBlendTable *table) {
   CDWriter cdata(_cycler);
   cdata->_transform_blend_table = (TransformBlendTable *)table;
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -396,7 +396,7 @@ set_slider_table(const SliderTable *table) {
 
   CDWriter cdata(_cycler);
   cdata->_slider_table = (SliderTable *)table;
-  clear_cache();
+  do_clear_cache(cdata);
   cdata->_modified = Geom::get_next_modified();
   cdata->_animated_vertices_modified = UpdateSeq();
 }
@@ -1069,26 +1069,6 @@ write(ostream &out, int indent_level) const {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GeomVertexData::clear_cache
-//       Access: Published
-//  Description: Removes all of the previously-cached results of
-//               convert_to().
-////////////////////////////////////////////////////////////////////
-void GeomVertexData::
-clear_cache() {
-  // Probably we shouldn't do anything at all here unless we are
-  // running in pipeline stage 0.
-  CData *cdata = CDWriter(_cycler);
-  for (Cache::iterator ci = cdata->_cache.begin();
-       ci != cdata->_cache.end();
-       ++ci) {
-    CacheEntry *entry = (*ci);
-    entry->erase();
-  }
-  cdata->_cache.clear();
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::get_array_info
 //       Access: Public
@@ -1263,7 +1243,7 @@ uint8_rgba_to_packed_argb(unsigned char *to, int to_stride,
 //  Description: The private implementation of set_num_rows().
 ////////////////////////////////////////////////////////////////////
 bool GeomVertexData::
-do_set_num_rows(int n, GeomVertexData::CDWriter &cdata) {
+do_set_num_rows(int n, GeomVertexData::CData *cdata) {
   nassertr(_format->get_num_arrays() == (int)cdata->_arrays.size(), false);
 
   bool any_changed = false;
@@ -1324,7 +1304,7 @@ do_set_num_rows(int n, GeomVertexData::CDWriter &cdata) {
   }
 
   if (any_changed) {
-    clear_cache();
+    do_clear_cache(cdata);
     cdata->_modified = Geom::get_next_modified();
     cdata->_animated_vertices.clear();
   }
@@ -1340,7 +1320,7 @@ do_set_num_rows(int n, GeomVertexData::CDWriter &cdata) {
 //               existing animated_vertices object.
 ////////////////////////////////////////////////////////////////////
 void GeomVertexData::
-update_animated_vertices(GeomVertexData::CDWriter &cdata) {
+update_animated_vertices(GeomVertexData::CData *cdata) {
   int num_rows = get_num_rows();
 
   if (gobj_cat.is_debug()) {
@@ -1474,6 +1454,24 @@ update_animated_vertices(GeomVertexData::CDWriter &cdata) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::do_clear_cache
+//       Access: Private
+//  Description: The private implementation of clear_cache().
+////////////////////////////////////////////////////////////////////
+void GeomVertexData::
+do_clear_cache(GeomVertexData::CData *cdata) {
+  // Probably we shouldn't do anything at all here unless we are
+  // running in pipeline stage 0.
+  for (Cache::iterator ci = cdata->_cache.begin();
+       ci != cdata->_cache.end();
+       ++ci) {
+    CacheEntry *entry = (*ci);
+    entry->erase();
+  }
+  cdata->_cache.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::register_with_read_factory
 //       Access: Public, Static

+ 8 - 4
panda/src/gobj/geomVertexData.h

@@ -142,7 +142,7 @@ PUBLISHED:
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
 
-  void clear_cache();
+  INLINE void clear_cache();
 
 public:
   bool get_array_info(const InternalName *name, 
@@ -191,7 +191,7 @@ private:
   INLINE static int 
   add_transform(TransformTable *table, const VertexTransform *transform,
                 TransformMap &already_added);
-  
+
 private:
   string _name;
   CPT(GeomVertexFormat) _format;
@@ -224,6 +224,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return GeomVertexData::get_class_type();
+    }
 
     UsageHint _usage_hint;
     Arrays _arrays;
@@ -241,8 +244,9 @@ private:
   typedef CycleDataWriter<CData> CDWriter;
 
 private:
-  bool do_set_num_rows(int n, CDWriter &cdata);
-  void update_animated_vertices(CDWriter &cdata);
+  bool do_set_num_rows(int n, CData *cdata);
+  void update_animated_vertices(CData *cdata);
+  void do_clear_cache(CData *cdata);
 
   static PStatCollector _convert_pcollector;
   static PStatCollector _scale_color_pcollector;

+ 3 - 0
panda/src/gobj/sliderTable.h

@@ -89,6 +89,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return SliderTable::get_class_type();
+    }
 
     UpdateSeq _modified;
   };

+ 3 - 0
panda/src/gobj/transformBlendTable.h

@@ -99,6 +99,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return TransformBlendTable::get_class_type();
+    }
 
     UpdateSeq _modified;
     UpdateSeq _global_modified;

+ 3 - 0
panda/src/gobj/transformTable.h

@@ -81,6 +81,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return TransformTable::get_class_type();
+    }
 
     UpdateSeq _modified;
   };

+ 3 - 0
panda/src/gobj/userVertexSlider.h

@@ -50,6 +50,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return UserVertexSlider::get_class_type();
+    }
 
     float _slider;
   };

+ 3 - 0
panda/src/gobj/userVertexTransform.h

@@ -55,6 +55,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return UserVertexTransform::get_class_type();
+    }
 
     LMatrix4f _matrix;
   };

+ 3 - 0
panda/src/gobj/vertexSlider.h

@@ -74,6 +74,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return VertexSlider::get_class_type();
+    }
 
     UpdateSeq _modified;
   };

+ 3 - 0
panda/src/gobj/vertexTransform.h

@@ -73,6 +73,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return VertexTransform::get_class_type();
+    }
 
     UpdateSeq _modified;
   };

+ 3 - 0
panda/src/parametrics/ropeNode.h

@@ -197,6 +197,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return RopeNode::get_class_type();
+    }
 
     PT(NurbsCurveEvaluator) _curve;
     RenderMode _render_mode;

+ 3 - 0
panda/src/parametrics/sheetNode.h

@@ -84,6 +84,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return SheetNode::get_class_type();
+    }
 
     PT(NurbsSurfaceEvaluator) _surface;
     bool _use_vertex_color;

+ 3 - 0
panda/src/pgraph/directionalLight.h

@@ -69,6 +69,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return DirectionalLight::get_class_type();
+    }
 
     Colorf _specular_color;
     LPoint3f _point;

+ 3 - 0
panda/src/pgraph/geomNode.h

@@ -111,6 +111,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return GeomNode::get_class_type();
+    }
 
     Geoms _geoms;
   };

+ 3 - 0
panda/src/pgraph/light.h

@@ -106,6 +106,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return Light::get_class_type();
+    }
 
     Colorf _color;
 

+ 3 - 0
panda/src/pgraph/lodNode.h

@@ -104,6 +104,9 @@ protected:
 
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return LODNode::get_class_type();
+    }
 
     LPoint3f _center;
     SwitchVector _switch_vector;

+ 3 - 0
panda/src/pgraph/nodePathComponent.h

@@ -82,6 +82,9 @@ private:
     INLINE CData();
     CData(const CData &copy);
     virtual CycleData *make_copy() const;
+    virtual TypeHandle get_parent_type() const {
+      return NodePathComponent::get_class_type();
+    }
 
     PT(NodePathComponent) _next;
     int _length;

+ 3 - 0
panda/src/pgraph/pandaNode.h

@@ -322,6 +322,9 @@ private:
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return PandaNode::get_class_type();
+    }
 
 #ifdef HAVE_PYTHON
     void inc_py_refs();

+ 3 - 0
panda/src/pgraph/planeNode.h

@@ -70,6 +70,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return PlaneNode::get_class_type();
+    }
 
     Planef _plane;
   };

+ 3 - 0
panda/src/pgraph/pointLight.h

@@ -69,6 +69,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return PointLight::get_class_type();
+    }
 
     Colorf _specular_color;
     LVecBase3f _attenuation;

+ 3 - 0
panda/src/pgraph/spotlight.h

@@ -89,6 +89,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return Spotlight::get_class_type();
+    }
 
     float _exponent;
     Colorf _specular_color;

+ 3 - 0
panda/src/pgraph/switchNode.h

@@ -54,6 +54,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return SwitchNode::get_class_type();
+    }
 
     int _visible_child;
   };

+ 3 - 0
panda/src/putil/animInterface.h

@@ -95,6 +95,9 @@ private:
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return AnimInterface::get_class_type();
+    }
 
     void play(double from, double to);
     void loop(bool restart, double from, double to);

+ 22 - 0
panda/src/putil/cycleData.cxx

@@ -85,3 +85,25 @@ void CycleData::
 fillin(DatagramIterator &, BamReader *, void *) {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CycleData::get_parent_type
+//       Access: Public, Virtual
+//  Description: Returns the type of the container that owns the
+//               CycleData.  This is useful mainly for debugging.
+////////////////////////////////////////////////////////////////////
+TypeHandle CycleData::
+get_parent_type() const {
+  return TypeHandle::none();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CycleData::output
+//       Access: Public, Virtual
+//  Description: Formats the contents of the CycleData in some
+//               meaningful way for humans.  This is useful mainly for
+//               debugging.
+////////////////////////////////////////////////////////////////////
+void CycleData::
+output(ostream &out) const {
+  out << get_parent_type() << "::CData";
+}

+ 10 - 1
panda/src/putil/cycleData.h

@@ -20,7 +20,7 @@
 #define CYCLEDATA_H
 
 #include "pandabase.h"
-
+#include "typeHandle.h"
 #include "referenceCount.h"
 
 class BamWriter;
@@ -64,8 +64,17 @@ public:
   virtual void fillin(DatagramIterator &scan, BamReader *manager);
   virtual void fillin(DatagramIterator &scan, BamReader *manager,
                       void *extra_data);
+
+  virtual TypeHandle get_parent_type() const;
+  virtual void output(ostream &out) const;
 };
 
+INLINE ostream &
+operator << (ostream &out, const CycleData &cd) {
+  cd.output(out);
+  return out;
+}
+
 #include "cycleData.I"
 
 #endif

+ 11 - 0
panda/src/putil/pipeline.I

@@ -29,3 +29,14 @@ get_render_pipeline() {
   }
   return _render_pipeline;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: Pipeline::set_min_stages
+//       Access: Public
+//  Description: Ensures that at least the indicated number of stages
+//               are in the pipeline.
+////////////////////////////////////////////////////////////////////
+INLINE void Pipeline::
+set_min_stages(int min_stages) {
+  set_num_stages(max(min_stages, get_num_stages()));
+}

+ 24 - 4
panda/src/putil/pipeline.cxx

@@ -18,6 +18,7 @@
 
 #include "pipeline.h"
 #include "pipelineCyclerTrueImpl.h"
+#include "mutexHolder.h"
 
 Pipeline *Pipeline::_render_pipeline = (Pipeline *)NULL;
 
@@ -53,6 +54,7 @@ Pipeline::
 void Pipeline::
 cycle() {
 #if defined(DO_PIPELINING) && defined(HAVE_THREADS)
+  MutexHolder holder(_lock);
   Cyclers::iterator ci;
   for (ci = _cyclers.begin(); ci != _cyclers.end(); ++ci) {
     (*ci)->cycle();
@@ -69,16 +71,32 @@ cycle() {
 void Pipeline::
 set_num_stages(int num_stages) {
   nassertv(num_stages >= 1);
-  if (num_stages != _num_stages) {
-    _num_stages = num_stages;
-
 #if defined(DO_PIPELINING) && defined(HAVE_THREADS)
+  MutexHolder holder(_lock);
+  if (num_stages != _num_stages) {
+    
+    // We need to lock every PipelineCycler object in the world before
+    // we can adjust the number of stages.
     Cyclers::iterator ci;
+    for (ci = _cyclers.begin(); ci != _cyclers.end(); ++ci) {
+      (*ci)->_lock.lock();
+    }
+      
+    _num_stages = num_stages;
+      
     for (ci = _cyclers.begin(); ci != _cyclers.end(); ++ci) {
       (*ci)->set_num_stages(num_stages);
     }
-#endif  // DO_PIPELINING && HAVE_THREADS
+    
+    // Now release them all.
+    for (ci = _cyclers.begin(); ci != _cyclers.end(); ++ci) {
+      (*ci)->_lock.release();
+    }
   }
+
+#else  // DO_PIPELINING && HAVE_THREADS
+  _num_stages = num_stages;
+#endif  // DO_PIPELINING && HAVE_THREADS
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -103,6 +121,7 @@ get_num_stages() const {
 ////////////////////////////////////////////////////////////////////
 void Pipeline::
 add_cycler(PipelineCyclerTrueImpl *cycler) {
+  MutexHolder holder(_lock);
   bool inserted = _cyclers.insert(cycler).second;
   nassertv(inserted);
 }
@@ -118,6 +137,7 @@ add_cycler(PipelineCyclerTrueImpl *cycler) {
 ////////////////////////////////////////////////////////////////////
 void Pipeline::
 remove_cycler(PipelineCyclerTrueImpl *cycler) {
+  MutexHolder holder(_lock);
   Cyclers::iterator ci = _cyclers.find(cycler);
   nassertv(ci != _cyclers.end());
   _cyclers.erase(ci);

+ 4 - 0
panda/src/putil/pipeline.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "namable.h"
 #include "pset.h"
+#include "pmutex.h"
 
 class PipelineCyclerTrueImpl;
 
@@ -48,6 +49,7 @@ public:
   void cycle();
 
   void set_num_stages(int num_stages);
+  INLINE void set_min_stages(int min_stages);
   int get_num_stages() const;
 
 #if defined(DO_PIPELINING) && defined(HAVE_THREADS)
@@ -64,6 +66,8 @@ private:
 #if defined(DO_PIPELINING) && defined(HAVE_THREADS)
   typedef pset<PipelineCyclerTrueImpl *> Cyclers;
   Cyclers _cyclers;
+
+  Mutex _lock;
 #endif  // DO_PIPELINING && HAVE_THREADS
 };
 

+ 40 - 22
panda/src/putil/pipelineCyclerTrueImpl.I

@@ -32,6 +32,7 @@ INLINE const CycleData *PipelineCyclerTrueImpl::
 read() const {
   int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
   nassertr(pipeline_stage >= 0 && pipeline_stage < _num_stages, NULL);
+  _lock.lock();
   return _data[pipeline_stage]._cycle_data;
 }
 
@@ -44,6 +45,12 @@ read() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PipelineCyclerTrueImpl::
 increment_read(const CycleData *pointer) const {
+#ifndef NDEBUG
+  int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
+  nassertv(pipeline_stage >= 0 && pipeline_stage < _num_stages);
+  nassertv(_data[pipeline_stage]._cycle_data == pointer);
+#endif
+  _lock.lock();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -54,6 +61,12 @@ increment_read(const CycleData *pointer) const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PipelineCyclerTrueImpl::
 release_read(const CycleData *pointer) const {
+#ifndef NDEBUG
+  int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
+  nassertv(pipeline_stage >= 0 && pipeline_stage < _num_stages);
+  nassertv(_data[pipeline_stage]._cycle_data == pointer);
+#endif
+  _lock.release();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -75,8 +88,7 @@ release_read(const CycleData *pointer) const {
 INLINE CycleData *PipelineCyclerTrueImpl::
 write() {
   int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
-  nassertr(pipeline_stage >= 0 && pipeline_stage < _num_stages, NULL);
-  return _data[pipeline_stage]._cycle_data;
+  return write_stage(pipeline_stage);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -90,7 +102,9 @@ write() {
 ////////////////////////////////////////////////////////////////////
 INLINE CycleData *PipelineCyclerTrueImpl::
 elevate_read(const CycleData *pointer) {
-  return (CycleData *)pointer;
+  CycleData *new_pointer = write();
+  _lock.release();
+  return new_pointer;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -101,6 +115,12 @@ elevate_read(const CycleData *pointer) {
 ////////////////////////////////////////////////////////////////////
 INLINE void PipelineCyclerTrueImpl::
 release_write(CycleData *pointer) {
+#ifdef NDEBUG
+  int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
+  return release_write_stage(pipeline_stage);
+#else
+  _lock.release();
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -123,23 +143,11 @@ get_num_stages() {
 INLINE bool PipelineCyclerTrueImpl::
 is_stage_unique(int n) const {
   nassertr(n >= 0 && n < _num_stages, false);
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PipelineCyclerTrueImpl::write_stage
-//       Access: Public
-//  Description: Returns a pointer suitable for writing to the nth
-//               stage of the pipeline.  This is for special
-//               applications that need to update the entire pipeline
-//               at once (for instance, to remove an invalid pointer).
-//               This pointer should later be released with
-//               release_write_stage().
-////////////////////////////////////////////////////////////////////
-INLINE CycleData *PipelineCyclerTrueImpl::
-write_stage(int n) {
-  nassertr(n >= 0 && n < _num_stages, (CycleData *)NULL);
-  return _data[n]._cycle_data;
+  if (n == 0) {
+    return true;
+  } else {
+    return _data[n]._cycle_data == _data[n - 1]._cycle_data;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -150,7 +158,11 @@ write_stage(int n) {
 ////////////////////////////////////////////////////////////////////
 INLINE void PipelineCyclerTrueImpl::
 release_write_stage(int n, CycleData *pointer) {
+#ifndef NDEBUG
   nassertv(n >= 0 && n < _num_stages);
+  nassertv(_data[n]._cycle_data == pointer);
+#endif
+  _lock.release();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -179,7 +191,10 @@ cheat() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int PipelineCyclerTrueImpl::
 get_read_count() const {
-  return 0;
+  int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
+  nassertr(pipeline_stage >= 0 && pipeline_stage < _num_stages, 0);
+  _lock.lock();
+  return _lock.get_lock_count();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -192,5 +207,8 @@ get_read_count() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int PipelineCyclerTrueImpl::
 get_write_count() const {
-  return 0;
+  int pipeline_stage = Thread::get_current_thread()->get_pipeline_stage();
+  nassertr(pipeline_stage >= 0 && pipeline_stage < _num_stages, 0);
+  _lock.lock();
+  return _lock.get_lock_count();
 }

+ 82 - 6
panda/src/putil/pipelineCyclerTrueImpl.cxx

@@ -20,7 +20,9 @@
 
 #if defined(DO_PIPELINING) && defined(HAVE_THREADS)
 
+#include "config_util.h"
 #include "pipeline.h"
+#include "reMutexHolder.h"
 
 
 ////////////////////////////////////////////////////////////////////
@@ -40,7 +42,7 @@ PipelineCyclerTrueImpl(CycleData *initial_data, Pipeline *pipeline) :
   _num_stages = _pipeline->get_num_stages();
   _data = new StageData[_num_stages];
   for (int i = 0; i < _num_stages; ++i) {
-    _data[i]._cycle_data = initial_data->make_copy();
+    _data[i]._cycle_data = initial_data;
   }
 }
 
@@ -55,6 +57,7 @@ PipelineCyclerTrueImpl(const PipelineCyclerTrueImpl &copy) :
 {
   _pipeline->add_cycler(this);
 
+  ReMutexHolder holder(copy._lock);
   _num_stages = _pipeline->get_num_stages();
   nassertv(_num_stages == copy._num_stages);
   _data = new StageData[_num_stages];
@@ -70,9 +73,12 @@ PipelineCyclerTrueImpl(const PipelineCyclerTrueImpl &copy) :
 ////////////////////////////////////////////////////////////////////
 void PipelineCyclerTrueImpl::
 operator = (const PipelineCyclerTrueImpl &copy) {
+  ReMutexHolder holder1(_lock);
+  ReMutexHolder holder2(copy._lock);
+
   nassertv(_num_stages == copy._num_stages);
   for (int i = 0; i < _num_stages; ++i) {
-    _data[i]._cycle_data = copy._data[i]._cycle_data->make_copy();
+    _data[i]._cycle_data = copy._data[i]._cycle_data;
   }
 }
 
@@ -83,11 +89,77 @@ operator = (const PipelineCyclerTrueImpl &copy) {
 ////////////////////////////////////////////////////////////////////
 PipelineCyclerTrueImpl::
 ~PipelineCyclerTrueImpl() {
+  ReMutexHolder holder(_lock);
   _pipeline->remove_cycler(this);
 
   delete[] _data;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCyclerTrueImpl::write_stage
+//       Access: Public
+//  Description: Returns a pointer suitable for writing to the nth
+//               stage of the pipeline.  This is for special
+//               applications that need to update the entire pipeline
+//               at once (for instance, to remove an invalid pointer).
+//               This pointer should later be released with
+//               release_write_stage().
+////////////////////////////////////////////////////////////////////
+CycleData *PipelineCyclerTrueImpl::
+write_stage(int n) {
+  _lock.lock();
+
+#ifndef NDEBUG
+  nassertd(n >= 0 && n < _num_stages) {
+    _lock.release();
+    return NULL;
+  }
+#endif  // NDEBUG
+
+  CycleData *old_data = _data[n]._cycle_data;
+
+  if (old_data->get_ref_count() != 1) {
+    // Copy-on-write.
+
+    // There's a special problem that happens when we write to a stage
+    // other than stage 0.  If we do this, when the next frame cycles,
+    // the changes that we record to stage n will be lost when the
+    // data from stage (n - 1) is cycled into place.  This can be
+    // wasteful, especially if we are updating a cached value (which
+    // is generally the case when we are writing to stages other than
+    // stage 0).
+
+    // To minimize this, we make a special exception: whenever we
+    // write to stage n, if stage (n - 1) has the same pointer, we
+    // will write to stage (n - 1) at the same time, and so on all the
+    // way back to stage 0 or the last different stage.
+
+    // On the other hand, if *all* of the instances of this pointer
+    // are found in stages k .. n, then we don't need to do anything
+    // at all.
+    int count = old_data->get_ref_count() - 1;
+    int k = n - 1;
+    while (k >= 0 && _data[k]._cycle_data == old_data) {
+      --k;
+      --count;
+    }
+
+    if (count > 0) {
+      PT(CycleData) new_data = old_data->make_copy();
+
+      int k = n - 1;
+      while (k >= 0 && _data[k]._cycle_data == old_data) {
+        _data[k]._cycle_data = new_data;
+        --k;
+      }
+      
+      _data[n]._cycle_data = new_data;
+    }
+  }
+
+  return _data[n]._cycle_data;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PipelineCyclerTrueImpl::cycle
 //       Access: Private
@@ -96,10 +168,11 @@ PipelineCyclerTrueImpl::
 ////////////////////////////////////////////////////////////////////
 void PipelineCyclerTrueImpl::
 cycle() {
+  ReMutexHolder holder(_lock);
+
   for (int i = _num_stages - 1; i > 0; --i) {
     _data[i] = _data[i - 1];
   }
-  _data[0]._cycle_data = _data[0]._cycle_data->make_copy();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -110,8 +183,11 @@ cycle() {
 ////////////////////////////////////////////////////////////////////
 void PipelineCyclerTrueImpl::
 set_num_stages(int num_stages) {
+  nassertv(_lock.debug_is_locked());
+
   if (num_stages <= _num_stages) {
-    // Don't bother to reallocate the array.
+    // Don't bother to reallocate the array smaller; we just won't use
+    // the rest of the array.
     for (int i = _num_stages; i < num_stages; ++i) {
       _data[i]._cycle_data.clear();
     }
@@ -120,14 +196,14 @@ set_num_stages(int num_stages) {
     
 
   } else {
-    // Increase the array.
+    // To increase the array, we must reallocate it larger.
     StageData *new_data = new StageData[num_stages];
     int i;
     for (i = 0; i < _num_stages; ++i) {
       new_data[i] = _data[i];
     }
     for (i = _num_stages; i < num_stages; ++i) {
-      new_data[i]._cycle_data = _data[_num_stages - 1]._cycle_data->make_copy();
+      new_data[i]._cycle_data = _data[_num_stages - 1]._cycle_data;
     }
     delete[] _data;
 

+ 4 - 1
panda/src/putil/pipelineCyclerTrueImpl.h

@@ -26,6 +26,7 @@
 #include "cycleData.h"
 #include "pointerTo.h"
 #include "thread.h"
+#include "reMutex.h"
 
 class Pipeline;
 
@@ -61,7 +62,7 @@ public:
 
   INLINE int get_num_stages();
   INLINE bool is_stage_unique(int n) const;
-  INLINE CycleData *write_stage(int n);
+  CycleData *write_stage(int n);
   INLINE void release_write_stage(int n, CycleData *pointer);
 
   INLINE CycleData *cheat() const;
@@ -83,6 +84,8 @@ private:
   StageData *_data;
   int _num_stages;
 
+  ReMutex _lock;
+
   friend class Pipeline;
 };