2
0
David Rose 21 жил өмнө
parent
commit
219f16c5de
40 өөрчлөгдсөн 1052 нэмэгдсэн , 213 устгасан
  1. 0 12
      panda/src/display/graphicsStateGuardian.I
  2. 24 18
      panda/src/display/graphicsStateGuardian.cxx
  3. 2 3
      panda/src/display/graphicsStateGuardian.h
  4. 12 13
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  5. 2 1
      panda/src/dxgsg8/dxGraphicsStateGuardian8.h
  6. 12 13
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  7. 2 1
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  8. 3 0
      panda/src/gobj/Sources.pp
  9. 9 0
      panda/src/gobj/config_gobj.cxx
  10. 2 0
      panda/src/gobj/config_gobj.h
  11. 1 1
      panda/src/gobj/drawable.cxx
  12. 3 1
      panda/src/gobj/drawable.h
  13. 1 1
      panda/src/gobj/geom.cxx
  14. 3 1
      panda/src/gobj/geom.h
  15. 1 0
      panda/src/gobj/gobj_composite1.cxx
  16. 5 3
      panda/src/gobj/qpgeom.cxx
  17. 2 1
      panda/src/gobj/qpgeom.h
  18. 33 5
      panda/src/gobj/qpgeomMunger.cxx
  19. 3 0
      panda/src/gobj/qpgeomMunger.h
  20. 130 3
      panda/src/gobj/qpgeomPrimitive.cxx
  21. 14 1
      panda/src/gobj/qpgeomPrimitive.h
  22. 13 13
      panda/src/gobj/qpgeomTrifans.cxx
  23. 3 2
      panda/src/gobj/qpgeomTrifans.h
  24. 13 13
      panda/src/gobj/qpgeomTristrips.cxx
  25. 3 2
      panda/src/gobj/qpgeomTristrips.h
  26. 179 0
      panda/src/gobj/qpgeomVertexCacheManager.I
  27. 148 0
      panda/src/gobj/qpgeomVertexCacheManager.cxx
  28. 113 0
      panda/src/gobj/qpgeomVertexCacheManager.h
  29. 233 95
      panda/src/gobj/qpgeomVertexData.cxx
  30. 23 6
      panda/src/gobj/qpgeomVertexData.h
  31. 2 0
      panda/src/gobj/qpgeomVertexFormat.cxx
  32. 5 0
      panda/src/gobj/qpgeomVertexFormat.h
  33. 3 0
      panda/src/gsgbase/graphicsStateGuardianBase.h
  34. 1 1
      panda/src/pgraph/cullHandler.I
  35. 3 3
      panda/src/pgraph/cullHandler.cxx
  36. 4 0
      panda/src/pgraph/cullResult.cxx
  37. 29 0
      panda/src/pgraph/cullableObject.I
  38. 7 0
      panda/src/pgraph/cullableObject.h
  39. 5 0
      panda/src/pgraph/drawCullHandler.cxx
  40. 1 0
      panda/src/pstatclient/pStatProperties.cxx

+ 0 - 12
panda/src/display/graphicsStateGuardian.I

@@ -285,16 +285,6 @@ get_scene() const {
   return _scene_setup;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::get_geom_munger
-//       Access: Public
-//  Description: Returns the current GeomMunger object.
-////////////////////////////////////////////////////////////////////
-INLINE const qpGeomMunger *GraphicsStateGuardian::
-get_geom_munger() const {
-  return _geom_munger;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::clear
 //       Access: Public
@@ -383,7 +373,6 @@ modify_state(const RenderState *state) {
     _state_pcollector.add_level(1);
     _state = _state->issue_delta_modify(state, this);
     finish_modify_state();
-    setup_geom_munger(NULL);
   }
 }
 
@@ -411,7 +400,6 @@ set_state(const RenderState *state) {
     _state_pcollector.add_level(1);
     _state = _state->issue_delta_set(state, this);
     finish_modify_state();
-    setup_geom_munger(NULL);
   }
 }
 

+ 24 - 18
panda/src/display/graphicsStateGuardian.cxx

@@ -267,6 +267,21 @@ void GraphicsStateGuardian::
 release_geom(GeomContext *) {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_geom_munger
+//       Access: Public, Virtual
+//  Description: Creates a new GeomMunger object to munge vertices
+//               appropriate to this GSG for the indicated state.
+////////////////////////////////////////////////////////////////////
+CPT(qpGeomMunger) GraphicsStateGuardian::
+get_geom_munger(const RenderState *) {
+  // The default implementation returns a munger that does nothing,
+  // but presumably, every kind of GSG needs some special munging
+  // action, so real GSG's will override this to return something more
+  // useful.
+  return qpGeomMunger::register_munger(new qpGeomMunger);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::set_state_and_transform
 //       Access: Public, Virtual
@@ -607,7 +622,7 @@ finish_decal() {
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
 begin_draw_primitives(const qpGeomVertexData *data) {
-  _vertex_data = get_geom_munger()->munge_data(data);
+  _vertex_data = data;
   return true;
 }
 
@@ -627,7 +642,10 @@ draw_triangles(qpGeomTriangles *) {
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
 draw_tristrips(qpGeomTristrips *primitive) {
-  primitive->decompose(_vertex_data)->draw(this);
+  PT(qpGeomPrimitive) new_prim = primitive->decompose();
+  if (!new_prim->is_of_type(qpGeomTristrips::get_class_type())) {
+    new_prim->draw(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -637,7 +655,10 @@ draw_tristrips(qpGeomTristrips *primitive) {
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
 draw_trifans(qpGeomTrifans *primitive) {
-  primitive->decompose(_vertex_data)->draw(this);
+  PT(qpGeomPrimitive) new_prim = primitive->decompose();
+  if (!new_prim->is_of_type(qpGeomTrifans::get_class_type())) {
+    new_prim->draw(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1329,21 +1350,6 @@ finish_modify_state() {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::setup_geom_munger
-//       Access: Protected, Virtual
-//  Description: Called after finish_modify_state has completed, this
-//               method sets up the GeomMunger for rendering with the
-//               current state.
-////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-setup_geom_munger(PT(qpGeomMunger) munger) {
-  if (munger == (qpGeomMunger *)NULL) {
-    munger = new qpGeomMunger;
-  }
-  _geom_munger = qpGeomMunger::register_munger(munger);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::free_pointers
 //       Access: Protected, Virtual

+ 2 - 3
panda/src/display/graphicsStateGuardian.h

@@ -97,7 +97,6 @@ PUBLISHED:
 public:
   INLINE bool set_scene(SceneSetup *scene_setup);
   INLINE SceneSetup *get_scene() const;
-  INLINE const qpGeomMunger *get_geom_munger() const;
 
   virtual PreparedGraphicsObjects *get_prepared_objects();
 
@@ -108,6 +107,8 @@ public:
   virtual GeomContext *prepare_geom(Geom *geom);
   virtual void release_geom(GeomContext *gc);
 
+  virtual CPT(qpGeomMunger) get_geom_munger(const RenderState *state);
+
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform);
 
@@ -209,7 +210,6 @@ protected:
   virtual void set_blend_mode();
 
   virtual void finish_modify_state();
-  virtual void setup_geom_munger(PT(qpGeomMunger) munger);
 
   virtual void free_pointers();
   virtual void close_gsg();
@@ -242,7 +242,6 @@ protected:
 
   CPT(RenderState) _state;
   CPT(TransformState) _transform;
-  CPT(qpGeomMunger) _geom_munger;
   CPT(qpGeomVertexData) _vertex_data;
 
   int _buffer_mask;

+ 12 - 13
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -2893,6 +2893,18 @@ release_texture(TextureContext *tc) {
   delete gtc;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::get_geom_munger
+//       Access: Public, Virtual
+//  Description: Creates a new GeomMunger object to munge vertices
+//               appropriate to this GSG for the indicated state.
+////////////////////////////////////////////////////////////////////
+CPT(qpGeomMunger) DXGraphicsStateGuardian8::
+get_geom_munger(const RenderState *) {
+  PT(DXGeomMunger8) munger = new DXGeomMunger8;
+  return qpGeomMunger::register_munger(munger);
+}
+
 // copies current display region in framebuffer to the texture
 // usually its more efficient to do SetRenderTgt
 void DXGraphicsStateGuardian8::
@@ -4052,19 +4064,6 @@ set_blend_mode() {
   enable_blend(false);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DXGraphicsStateGuardian8::setup_geom_munger
-//       Access: Protected, Virtual
-//  Description: Called after finish_modify_state has completed, this
-//               method sets up the GeomMunger for rendering with the
-//               current state.
-////////////////////////////////////////////////////////////////////
-void DXGraphicsStateGuardian8::
-setup_geom_munger(PT(qpGeomMunger) munger) {
-  munger = new DXGeomMunger8;
-  GraphicsStateGuardian::setup_geom_munger(munger);
-}
-
 TypeHandle DXGraphicsStateGuardian8::get_type(void) const {
     return get_class_type();
 }

+ 2 - 1
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -97,6 +97,8 @@ public:
   virtual void apply_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
 
+  virtual CPT(qpGeomMunger) get_geom_munger(const RenderState *state);
+
   virtual void framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                                            const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
@@ -152,7 +154,6 @@ protected:
   virtual void bind_clip_plane(PlaneNode *plane, int plane_id);
 
   virtual void set_blend_mode();
-  virtual void setup_geom_munger(PT(qpGeomMunger) munger);
 
   void free_nondx_resources();            // free local internal buffers
   void free_d3d_device(void);

+ 12 - 13
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2326,6 +2326,18 @@ static int binary_log_cap(const int x) {
 }
 #endif
 
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_geom_munger
+//       Access: Public, Virtual
+//  Description: Creates a new GeomMunger object to munge vertices
+//               appropriate to this GSG for the indicated state.
+////////////////////////////////////////////////////////////////////
+CPT(qpGeomMunger) CLP(GraphicsStateGuardian)::
+get_geom_munger(const RenderState *) {
+  PT(CLP(GeomMunger)) munger = new CLP(GeomMunger);
+  return qpGeomMunger::register_munger(munger);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::framebuffer_copy_to_texture
 //       Access: Public, Virtual
@@ -5036,19 +5048,6 @@ finish_modify_state() {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: CLP(GraphicsStateGuardian)::setup_geom_munger
-//       Access: Protected, Virtual
-//  Description: Called after finish_modify_state has completed, this
-//               method sets up the GeomMunger for rendering with the
-//               current state.
-////////////////////////////////////////////////////////////////////
-void CLP(GraphicsStateGuardian)::
-setup_geom_munger(PT(qpGeomMunger) munger) {
-  munger = new CLP(GeomMunger);
-  GraphicsStateGuardian::setup_geom_munger(munger);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::free_pointers
 //       Access: Protected, Virtual

+ 2 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -102,6 +102,8 @@ public:
   virtual GeomContext *prepare_geom(Geom *geom);
   virtual void release_geom(GeomContext *gc);
 
+  virtual CPT(qpGeomMunger) get_geom_munger(const RenderState *state);
+
   virtual void framebuffer_copy_to_texture
     (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
@@ -180,7 +182,6 @@ protected:
   virtual void set_blend_mode();
 
   virtual void finish_modify_state();
-  virtual void setup_geom_munger(PT(qpGeomMunger) munger);
 
   virtual void free_pointers();
 

+ 3 - 0
panda/src/gobj/Sources.pp

@@ -24,6 +24,7 @@
     qpgeomTristrips.h \
     qpgeomTrifans.h \
     qpgeomVertexArrayFormat.h qpgeomVertexArrayFormat.I \
+    qpgeomVertexCacheManager.h qpgeomVertexCacheManager.I \
     qpgeomVertexData.h qpgeomVertexData.I \
     qpgeomVertexDataType.h qpgeomVertexDataType.I \
     qpgeomVertexFormat.h qpgeomVertexFormat.I \
@@ -55,6 +56,7 @@
     qpgeomTristrips.cxx \
     qpgeomTrifans.cxx \
     qpgeomVertexArrayFormat.cxx \
+    qpgeomVertexCacheManager.cxx \
     qpgeomVertexData.cxx \
     qpgeomVertexDataType.cxx \
     qpgeomVertexFormat.cxx \
@@ -84,6 +86,7 @@
     qpgeomTristrips.h \
     qpgeomTrifans.h \
     qpgeomVertexArrayFormat.h qpgeomVertexArrayFormat.I \
+    qpgeomVertexCacheManager.h qpgeomVertexCacheManager.I \
     qpgeomVertexData.h qpgeomVertexData.I \
     qpgeomVertexDataType.h qpgeomVertexDataType.I \
     qpgeomVertexFormat.h qpgeomVertexFormat.I \

+ 9 - 0
panda/src/gobj/config_gobj.cxx

@@ -116,6 +116,15 @@ ConfigVariableString fake_texture_image
           "the same texture file, which will presumably only be loaded "
           "once."));
 
+ConfigVariableInt vertex_convert_cache
+("vertex-convert-cache", 4194304, // 4 MB
+ PRC_DESC("This is the amount of memory, in bytes, that should be set "
+          "aside for storing pre-processed data for rendering vertices.  "
+          "This is not a limit on the actual vertex data, which is "
+          "determined by the model; it is also not a limit on the "
+          "amount of memory used by the video driver or the system "
+          "graphics interface, which Panda has no control over."));
+
 ConfigVariableDouble default_near
 ("default-near", 1.0,
  PRC_DESC("The default near clipping distance for all cameras."));

+ 2 - 0
panda/src/gobj/config_gobj.h

@@ -59,6 +59,8 @@ extern EXPCL_PANDA ConfigVariableEnum<AutoTextureScale> textures_power_2;
 extern EXPCL_PANDA ConfigVariableEnum<AutoTextureScale> textures_square;
 extern EXPCL_PANDA ConfigVariableString fake_texture_image;
 
+extern EXPCL_PANDA ConfigVariableInt vertex_convert_cache;
+
 extern ConfigVariableDouble default_near;
 extern ConfigVariableDouble default_far;
 extern ConfigVariableDouble default_fov;

+ 1 - 1
panda/src/gobj/drawable.cxx

@@ -48,7 +48,7 @@ dDrawable::
 //               At this level, this doesn't do very much.
 ////////////////////////////////////////////////////////////////////
 void dDrawable::
-draw(GraphicsStateGuardianBase *) const { 
+draw(GraphicsStateGuardianBase *, const qpGeomVertexData *) const { 
   if (is_dirty()) {
     ((dDrawable *)this)->config(); 
   }

+ 3 - 1
panda/src/gobj/drawable.h

@@ -34,6 +34,7 @@ class Datagram;
 class DatagramIterator;
 class BamReader;
 class BamWriter;
+class qpGeomVertexData;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Drawable
@@ -50,7 +51,8 @@ public:
   dDrawable();
   virtual ~dDrawable();
 
-  virtual void draw(GraphicsStateGuardianBase *) const;
+  virtual void draw(GraphicsStateGuardianBase *gsg, 
+                    const qpGeomVertexData *vertex_data) const;
   virtual bool is_dynamic() const;
 
 protected:

+ 1 - 1
panda/src/gobj/geom.cxx

@@ -915,7 +915,7 @@ release_all() {
 //  Description: Actually draws the Geom with the indicated GSG.
 ////////////////////////////////////////////////////////////////////
 void Geom::
-draw(GraphicsStateGuardianBase *gsg) const {
+draw(GraphicsStateGuardianBase *gsg, const qpGeomVertexData *) const {
   PreparedGraphicsObjects *prepared_objects = gsg->get_prepared_objects();
   if (is_dirty()) {
     ((Geom *)this)->config(); 

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

@@ -43,6 +43,7 @@ class DatagramIterator;
 class BamReader;
 class BamWriter;
 class GeomContext;
+class qpGeomVertexData;
 class PreparedGraphicsObjects;
 
 ////////////////////////////////////////////////////////////////////
@@ -239,7 +240,8 @@ public:
   int release_all();
 
   // From parent dDrawable
-  virtual void draw(GraphicsStateGuardianBase *gsg) const;
+  virtual void draw(GraphicsStateGuardianBase *gsg, 
+                    const qpGeomVertexData *vertex_data) const;
 
   // From parent Configurable
   virtual void config();

+ 1 - 0
panda/src/gobj/gobj_composite1.cxx

@@ -18,6 +18,7 @@
 #include "qpgeomTristrips.cxx"
 #include "qpgeomTrifans.cxx"
 #include "qpgeomVertexArrayFormat.cxx"
+#include "qpgeomVertexCacheManager.cxx"
 #include "qpgeomVertexData.cxx"
 #include "qpgeomVertexDataType.cxx"
 #include "qpgeomVertexFormat.cxx"

+ 5 - 3
panda/src/gobj/qpgeom.cxx

@@ -203,13 +203,15 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeom::draw
 //       Access: Public
-//  Description: Actually draws the Geom with the indicated GSG.
+//  Description: Actually draws the Geom with the indicated GSG, using
+//               the indicated vertex data (which might have been
+//               pre-munged to support the GSG's needs).
 ////////////////////////////////////////////////////////////////////
 void qpGeom::
-draw(GraphicsStateGuardianBase *gsg) const {
+draw(GraphicsStateGuardianBase *gsg, const qpGeomVertexData *vertex_data) const {
   CDReader cdata(_cycler);
 
-  if (gsg->begin_draw_primitives(cdata->_data)) {
+  if (gsg->begin_draw_primitives(vertex_data)) {
     Primitives::const_iterator pi;
     for (pi = cdata->_primitives.begin(); 
          pi != cdata->_primitives.end();

+ 2 - 1
panda/src/gobj/qpgeom.h

@@ -76,7 +76,8 @@ PUBLISHED:
   void write(ostream &out, int indent_level = 0) const;
 
 public:
-  void draw(GraphicsStateGuardianBase *gsg) const;
+  void draw(GraphicsStateGuardianBase *gsg, 
+            const qpGeomVertexData *vertex_data) const;
 
 protected:
   virtual BoundingVolume *recompute_bound();

+ 33 - 5
panda/src/gobj/qpgeomMunger.cxx

@@ -17,10 +17,15 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "qpgeomMunger.h"
+#include "qpgeomVertexCacheManager.h"
+#include "mutexHolder.h"
+#include "pStatTimer.h"
 
 qpGeomMunger::Registry *qpGeomMunger::_registry = NULL;
 TypeHandle qpGeomMunger::_type_handle;
 
+PStatCollector qpGeomMunger::_munge_pcollector("Cull:Munge");
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::Constructor
 //       Access: Public
@@ -92,6 +97,7 @@ remove_data(const qpGeomVertexData *data) {
 CPT(qpGeomVertexData) qpGeomMunger::
 do_munge_data(const qpGeomVertexData *data) {
   nassertr(_is_registered, NULL);
+  PStatTimer timer(_munge_pcollector);
 
   CPT(qpGeomVertexFormat) orig_format = data->get_format();
   CPT(qpGeomVertexFormat) new_format = munge_format(orig_format);
@@ -132,8 +138,12 @@ do_munge_format(const qpGeomVertexFormat *format) {
   nassertr(inserted, NULL);
 
   // Tell the source format that we have its pointer.
-  inserted = ((qpGeomVertexFormat *)format)->_mungers.insert(this).second;
-  nassertr(inserted, NULL);
+  {
+    qpGeomVertexFormat *f = (qpGeomVertexFormat *)format;
+    MutexHolder holder(f->_cache_lock);
+    inserted = f->_mungers.insert(this).second;
+    nassertr(inserted, NULL);
+  }
 
   // We also want to keep a reference count on the derived format, but
   // only if it is actually a different pointer from the original
@@ -189,9 +199,20 @@ make_registry() {
 ////////////////////////////////////////////////////////////////////
 void qpGeomMunger::
 do_register() {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "GeomMunger::do_register(): " << (void *)this << "\n";
+  }
   nassertv(!_is_registered);
   nassertv(_formats.empty());
 
+  // Tell the cache manager to hang on to this new GeomMunger, so we
+  // don't waste our time re-registering the same GeomMunger over and
+  // over again.
+  qpGeomVertexCacheManager *cache_mgr =
+    qpGeomVertexCacheManager::get_global_ptr();
+  cache_mgr->record_munger(this);
+
   _is_registered = true;
 }
  
@@ -202,17 +223,24 @@ do_register() {
 ////////////////////////////////////////////////////////////////////
 void qpGeomMunger::
 do_unregister() {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "GeomMunger::do_unregister(): " << (void *)this << "\n";
+  }
   nassertv(_is_registered);
   _is_registered = false;
 
   // Unregistering means we should blow away the cache.
   Formats::iterator fi;
   for (fi = _formats.begin(); fi != _formats.end(); ++fi) {
-    const qpGeomVertexFormat *format = (*fi).first;
+    qpGeomVertexFormat *format = (qpGeomVertexFormat *)(*fi).first;
     CPT(qpGeomVertexFormat) derived_format = (*fi).second;
 
-    size_t num_erased = ((qpGeomVertexFormat *)format)->_mungers.erase(this);
-    nassertv(num_erased == 1);
+    {
+      MutexHolder holder(format->_cache_lock);
+      size_t num_erased = format->_mungers.erase(this);
+      nassertv(num_erased == 1);
+    }
 
     if (derived_format != format) {
       derived_format->unref();

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

@@ -23,6 +23,7 @@
 #include "typedReferenceCount.h"
 #include "qpgeomVertexFormat.h"
 #include "indirectCompareTo.h"
+#include "pStatCollector.h"
 #include "pmap.h"
 #include "pset.h"
 
@@ -97,6 +98,8 @@ private:
 
   static Registry *_registry;
 
+  static PStatCollector _munge_pcollector;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 130 - 3
panda/src/gobj/qpgeomPrimitive.cxx

@@ -20,6 +20,7 @@
 #include "qpgeomVertexData.h"
 #include "qpgeomVertexArrayFormat.h"
 #include "qpgeomVertexDataType.h"
+#include "qpgeomVertexCacheManager.h"
 #include "internalName.h"
 #include "bamReader.h"
 #include "bamWriter.h"
@@ -55,6 +56,23 @@ qpGeomPrimitive(const qpGeomPrimitive &copy) :
 ////////////////////////////////////////////////////////////////////
 qpGeomPrimitive::
 ~qpGeomPrimitive() {
+  // When we destruct, we should ensure that all of our cached
+  // entries, across all pipeline stages, are properly removed from
+  // the cache manager.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  int num_stages = _cycler.get_num_stages();
+  for (int i = 0; i < num_stages; i++) {
+    if (_cycler.is_stage_unique(i)) {
+      CData *cdata = _cycler.write_stage(i);
+      if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
+        cache_mgr->remove_decompose(this);
+        cdata->_decomposed = NULL;
+      }
+      _cycler.release_write_stage(i, cdata);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -209,6 +227,19 @@ set_lengths(PTA_int lengths) {
   cdata->_lengths = lengths;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_num_bytes
+//       Access: Published
+//  Description: Records the number of bytes consumed by the primitive
+//               and its index table(s).
+////////////////////////////////////////////////////////////////////
+int qpGeomPrimitive::
+get_num_bytes() const {
+  CDReader cdata(_cycler);
+  return cdata->_vertices.size() * sizeof(short) +
+    cdata->_lengths.size() * sizeof(int);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_num_vertices_per_primitive
 //       Access: Published, Virtual
@@ -310,7 +341,7 @@ get_primitive_num_vertices(int i) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::decompose
-//       Access: Published, Virtual
+//       Access: Published
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
 //               triangles, and returns a pointer to the new primitive
@@ -323,8 +354,48 @@ get_primitive_num_vertices(int i) const {
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
 PT(qpGeomPrimitive) qpGeomPrimitive::
-decompose(const qpGeomVertexData *vertex_data) {
-  return this;
+decompose() {
+  PT(qpGeomPrimitive) result;
+  {
+    // Use read() and release_read() instead of CDReader, because the
+    // call to record_decompose() might recursively call back into
+    // this object, and require a write.
+    const CData *cdata = _cycler.read();
+    if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
+      result = cdata->_decomposed;
+      _cycler.release_read(cdata);
+      // Record a cache hit, so this element will stay in the cache a
+      // while longer.
+      qpGeomVertexCacheManager *cache_mgr = 
+        qpGeomVertexCacheManager::get_global_ptr();
+      cache_mgr->record_decompose(this, result->get_num_bytes());
+
+      return result;
+    }
+    _cycler.release_read(cdata);
+  }
+
+  result = decompose_impl();
+  if (result == this) {
+    // decomposing this primitive has no effect.
+    return this;
+  }
+
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Decomposing " << get_type() << ": " << (void *)this << "\n";
+  }
+
+  // Record the result for the future.
+  CDWriter cdata(_cycler);
+  cdata->_decomposed = result;
+
+  // And add *this* object to the cache manager.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+  cache_mgr->record_decompose(this, result->get_num_bytes());
+
+  return result;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -361,6 +432,26 @@ write(ostream &out, const qpGeomVertexData *vertex_data,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::clear_cache
+//       Access: Published
+//  Description: Removes all of the previously-cached results of
+//               convert_to().
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+clear_cache() {
+  // Probably we shouldn't do anything at all here unless we are
+  // running in pipeline stage 0.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  CData *cdata = CDWriter(_cycler);
+  if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
+    cache_mgr->remove_decompose(this);
+    cdata->_decomposed = NULL;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::calc_tight_bounds
 //       Access: Public, Virtual
@@ -417,6 +508,42 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::decompose_impl
+//       Access: Protected
+//  Description: Decomposes a complex primitive type into a simpler
+//               primitive type, for instance triangle strips to
+//               triangles, and returns a pointer to the new primitive
+//               definition.  If the decomposition cannot be
+//               performed, this might return the original object.
+//
+//               This method is useful for application code that wants
+//               to iterate through the set of triangles on the
+//               primitive without having to write handlers for each
+//               possible kind of primitive type.
+////////////////////////////////////////////////////////////////////
+PT(qpGeomPrimitive) qpGeomPrimitive::
+decompose_impl() {
+  return this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::remove_cache_entry
+//       Access: Private
+//  Description: Removes a particular entry from the local cache; it
+//               has already been removed from the cache manager.
+//               This is only called from GeomVertexCacheManager.
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+remove_cache_entry() const {
+  // We have to operate on stage 0 of the pipeline, since that's where
+  // the cache really counts.  Because of the multistage pipeline, we
+  // might not actually have a cache entry there (it might have been
+  // added to stage 1 instead).  No big deal if we don't.
+  CData *cdata = ((qpGeomPrimitive *)this)->_cycler.write_stage(0);
+  cdata->_decomposed = NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::recompute_minmax
 //       Access: Private

+ 14 - 1
panda/src/gobj/qpgeomPrimitive.h

@@ -79,6 +79,8 @@ PUBLISHED:
   PTA_int modify_lengths();
   void set_lengths(PTA_int lengths);
 
+  int get_num_bytes() const;
+
   INLINE int get_min_vertex() const;
   INLINE int get_max_vertex() const;
 
@@ -87,12 +89,14 @@ PUBLISHED:
   int get_primitive_start(int i) const;
   int get_primitive_num_vertices(int i) const;
 
-  virtual PT(qpGeomPrimitive) decompose(const qpGeomVertexData *vertex_data);
+  PT(qpGeomPrimitive) decompose();
 
   virtual void output(ostream &out, const qpGeomVertexData *vertex_data) const;
   virtual void write(ostream &out, const qpGeomVertexData *vertex_data, 
                      int indent_level) const;
 
+  void clear_cache();
+
 public:
   virtual void draw(GraphicsStateGuardianBase *gsg)=0;
 
@@ -100,6 +104,12 @@ public:
                                  bool &found_any, 
                                  const qpGeomVertexData *vertex_data) const;
 
+protected:
+  virtual PT(qpGeomPrimitive) decompose_impl();
+
+private:
+  void remove_cache_entry() const;
+
 private:
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
@@ -117,6 +127,8 @@ private:
     bool _got_minmax;
     unsigned short _min_vertex;
     unsigned short _max_vertex;
+
+    PT(qpGeomPrimitive) _decomposed;
   };
 
   PipelineCycler<CData> _cycler;
@@ -149,6 +161,7 @@ private:
   static TypeHandle _type_handle;
 
   friend class qpGeom;
+  friend class qpGeomVertexCacheManager;
 };
 
 #include "qpgeomPrimitive.I"

+ 13 - 13
panda/src/gobj/qpgeomTrifans.cxx

@@ -53,7 +53,18 @@ qpGeomTrifans::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomTrifans::decompose
+//     Function: qpGeomTrifans::draw
+//       Access: Public, Virtual
+//  Description: Calls the appropriate method on the GSG to draw the
+//               primitive.
+////////////////////////////////////////////////////////////////////
+void qpGeomTrifans::
+draw(GraphicsStateGuardianBase *gsg) {
+  gsg->draw_trifans(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTrifans::decompose_impl
 //       Access: Published, Virtual
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
@@ -67,7 +78,7 @@ qpGeomTrifans::
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
 PT(qpGeomPrimitive) qpGeomTrifans::
-decompose(const qpGeomVertexData *) {
+decompose_impl() {
   PT(qpGeomTriangles) triangles = new qpGeomTriangles;
   CPTA_ushort vertices = get_vertices();
   CPTA_int lengths = get_lengths();
@@ -95,17 +106,6 @@ decompose(const qpGeomVertexData *) {
   return triangles.p();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomTrifans::draw
-//       Access: Public, Virtual
-//  Description: Calls the appropriate method on the GSG to draw the
-//               primitive.
-////////////////////////////////////////////////////////////////////
-void qpGeomTrifans::
-draw(GraphicsStateGuardianBase *gsg) {
-  gsg->draw_trifans(this);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTrifans::register_with_read_factory
 //       Access: Public, Static

+ 3 - 2
panda/src/gobj/qpgeomTrifans.h

@@ -34,11 +34,12 @@ PUBLISHED:
   qpGeomTrifans(const qpGeomTrifans &copy);
   virtual ~qpGeomTrifans();
 
-  virtual PT(qpGeomPrimitive) decompose(const qpGeomVertexData *vertex_data);
-
 public:
   virtual void draw(GraphicsStateGuardianBase *gsg);
 
+protected:
+  virtual PT(qpGeomPrimitive) decompose_impl();
+
 public:
   static void register_with_read_factory();
 

+ 13 - 13
panda/src/gobj/qpgeomTristrips.cxx

@@ -53,7 +53,18 @@ qpGeomTristrips::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomTristrips::decompose
+//     Function: qpGeomTristrips::draw
+//       Access: Public, Virtual
+//  Description: Calls the appropriate method on the GSG to draw the
+//               primitive.
+////////////////////////////////////////////////////////////////////
+void qpGeomTristrips::
+draw(GraphicsStateGuardianBase *gsg) {
+  gsg->draw_tristrips(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTristrips::decompose_impl
 //       Access: Published, Virtual
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
@@ -67,7 +78,7 @@ qpGeomTristrips::
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
 PT(qpGeomPrimitive) qpGeomTristrips::
-decompose(const qpGeomVertexData *) {
+decompose_impl() {
   PT(qpGeomTriangles) triangles = new qpGeomTriangles;
   CPTA_ushort vertices = get_vertices();
   CPTA_int lengths = get_lengths();
@@ -105,17 +116,6 @@ decompose(const qpGeomVertexData *) {
   return triangles.p();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomTristrips::draw
-//       Access: Public, Virtual
-//  Description: Calls the appropriate method on the GSG to draw the
-//               primitive.
-////////////////////////////////////////////////////////////////////
-void qpGeomTristrips::
-draw(GraphicsStateGuardianBase *gsg) {
-  gsg->draw_tristrips(this);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTristrips::register_with_read_factory
 //       Access: Public, Static

+ 3 - 2
panda/src/gobj/qpgeomTristrips.h

@@ -34,11 +34,12 @@ PUBLISHED:
   qpGeomTristrips(const qpGeomTristrips &copy);
   virtual ~qpGeomTristrips();
 
-  virtual PT(qpGeomPrimitive) decompose(const qpGeomVertexData *vertex_data);
-
 public:
   virtual void draw(GraphicsStateGuardianBase *gsg);
 
+protected:
+  virtual PT(qpGeomPrimitive) decompose_impl();
+
 public:
   static void register_with_read_factory();
 

+ 179 - 0
panda/src/gobj/qpgeomVertexCacheManager.I

@@ -0,0 +1,179 @@
+// Filename: qpgeomVertexCacheManager.I
+// Created by:  drose (11Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::set_max_size
+//       Access: Published
+//  Description: Specifies the amount of memory, in bytes, that should
+//               be set aside for storing pre-processed data for
+//               rendering vertices.  This is not a limit on the
+//               actual vertex data, which is what it is; it is also
+//               not a limit on the amount of memory used by the video
+//               driver or the system graphics interface, which Panda
+//               has no control over.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+set_max_size(int max_size) const {
+  // We directly change the config variable.
+  vertex_convert_cache = max_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::get_max_size
+//       Access: Published
+//  Description: Returns the amount of memory, in bytes, that should
+//               be set aside for storing pre-processed data for
+//               rendering vertices.  See set_max_size().
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomVertexCacheManager::
+get_max_size() const {
+  return vertex_convert_cache;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::get_total_size
+//       Access: Published
+//  Description: Returns the amount of memory, in bytes, currently
+//               consumed by the cache of pre-processed vertex data.
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomVertexCacheManager::
+get_total_size() const {
+  return _total_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::record_munger
+//       Access: Private
+//  Description: Records a new GeomMunger in the cache, or marks a
+//               cache hit for a previously-recorded munger.  This
+//               should only be called by GeomMunger.
+//
+//               The cache manager will hold a reference on the
+//               GeomMunger pointer until it expires from the cache.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+record_munger(const qpGeomMunger *munger) {
+  Entry entry;
+  entry._munger = munger;
+  entry._primitive = NULL;
+  entry._source = NULL;
+  entry._format = NULL;
+  entry._result_size = 100;  // Make up a nominal number.
+  
+  record_entry(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::record_decompose
+//       Access: Private
+//  Description: Records a new entry in the cache, or marks a cache
+//               hit for a previous entry in the cache.  This should
+//               only be called by GeomPrimitive.
+//
+//               The cache manager will not hold a reference on the
+//               GeomPrimitive pointer; if it destructs, it should
+//               call remove_decompose() to remove itself from the cache.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+record_decompose(const qpGeomPrimitive *primitive, int result_size) {
+  Entry entry;
+  entry._primitive = primitive;
+  entry._source = NULL;
+  entry._format = NULL;
+  entry._result_size = result_size;
+  
+  record_entry(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::remove_decompose
+//       Access: Private
+//  Description: Removes an entry from the cache, if it is there.
+//               Quietly ignores it if it is not.  This should only be
+//               called by GeomPrimitive.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+remove_decompose(const qpGeomPrimitive *primitive) {
+  Entry entry;
+  entry._primitive = primitive;
+  entry._source = NULL;
+  entry._format = NULL;
+
+  remove_entry(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::record_data
+//       Access: Private
+//  Description: Records a new entry in the cache, or marks a cache
+//               hit for a previous entry in the cache.  This should
+//               only be called by GeomVertexData.
+//
+//               The cache manager will not hold a reference on the
+//               GeomVertexData pointer; if it destructs, it should
+//               call remove_data() to remove itself from the cache.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+record_data(const qpGeomVertexData *source, const qpGeomVertexFormat *format,
+            int result_size) {
+  Entry entry;
+  entry._primitive = NULL;
+  entry._source = source;
+  entry._format = format;
+  entry._result_size = result_size;
+  
+  record_entry(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::remove_data
+//       Access: Private
+//  Description: Removes an entry from the cache, if it is there.
+//               Quietly ignores it if it is not.  This should only be
+//               called by GeomVertexData.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+remove_data(const qpGeomVertexData *source,
+            const qpGeomVertexFormat *format) {
+  Entry entry;
+  entry._primitive = NULL;
+  entry._source = source;
+  entry._format = format;
+
+  remove_entry(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::operator <
+//       Access: Public
+//  Description: Provides a unique ordering for EntriesIndex.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomVertexCacheManager::Entry::
+operator < (const qpGeomVertexCacheManager::Entry &other) const {
+  if (_munger != other._munger) {
+    return _munger < other._munger;
+  }
+  if (_primitive != other._primitive) {
+    return _primitive < other._primitive;
+  }
+  if (_source != other._source) {
+    return _source < other._source;
+  }
+  return _format < other._format;
+}

+ 148 - 0
panda/src/gobj/qpgeomVertexCacheManager.cxx

@@ -0,0 +1,148 @@
+// Filename: qpgeomVertexCacheManager.cxx
+// Created by:  drose (11Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qpgeomVertexCacheManager.h"
+#include "mutexHolder.h"
+
+qpGeomVertexCacheManager *qpGeomVertexCacheManager::_global_ptr = NULL;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpGeomVertexCacheManager::
+qpGeomVertexCacheManager() :
+  _total_size(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Destructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpGeomVertexCacheManager::
+~qpGeomVertexCacheManager() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::get_global_ptr
+//       Access: Published, Static
+//  Description: Returns the global cache manager pointer.
+////////////////////////////////////////////////////////////////////
+qpGeomVertexCacheManager *qpGeomVertexCacheManager::
+get_global_ptr() {
+  if (_global_ptr == (qpGeomVertexCacheManager *)NULL) {
+    _global_ptr = new qpGeomVertexCacheManager;
+  }
+  return _global_ptr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::record_data
+//       Access: Private
+//  Description: Records a new generic entry in the cache, or marks a
+//               cache hit for a previous entry in the cache.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexCacheManager::
+record_entry(const qpGeomVertexCacheManager::Entry &entry) {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "record_entry(" << entry._munger << ", " 
+      << entry._primitive << ", " << entry._source << ", " 
+      << entry._format << ", " << entry._result_size << ")\n";
+  }
+
+  MutexHolder holder(_lock);
+  EntriesIndex::iterator ii = _entries_index.find(&entry);
+  if (ii != _entries_index.end()) {
+    // Remove the previous cache entry.
+    Entries::iterator ei = (*ii).second;
+    _total_size -= (*ei)._result_size;
+    _entries_index.erase(ii);
+    _entries.erase(ei);
+  }
+
+  // Add the new entry at the end of the list, so it will be the last
+  // to be removed.
+  Entries::iterator ei = 
+    _entries.insert(_entries.end(), entry);
+  Entry *entry_pointer = &(*ei);
+
+  // Also record an index entry.
+  bool inserted = _entries_index.insert
+    (EntriesIndex::value_type(entry_pointer, ei)).second;
+  nassertv(inserted);
+
+  _total_size += entry_pointer->_result_size;
+
+  // Now remove any old entries if our cache is over the limit.  This may
+  // also remove the entry we just added, especially if our cache size
+  // is set to 0.
+  int max_size = get_max_size();
+  while (_total_size > max_size) {
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << "total_size = " << _total_size << ", max_size = "
+        << max_size << ", flushing\n";
+    }
+    nassertv(!_entries.empty());
+    ei = _entries.begin();
+    entry_pointer = &(*ei);
+
+    ii = _entries_index.find(entry_pointer);
+    nassertv(ii != _entries_index.end());
+
+    if (entry_pointer->_source != (qpGeomVertexData *)NULL) {
+      entry_pointer->_source->remove_cache_entry(entry_pointer->_format);
+    }
+    _total_size -= entry_pointer->_result_size;
+    _entries_index.erase(ii);
+    _entries.erase(ei);
+  }
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "total_size = " << _total_size << ", max_size = "
+      << max_size << ", done\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::remove_entry
+//       Access: Private
+//  Description: Removes an entry from the cache, if it is there.
+//               Quietly ignores it if it is not.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexCacheManager::
+remove_entry(const qpGeomVertexCacheManager::Entry &entry) {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "remove_entry(" << entry._munger << ", "
+      << entry._source << ", " << entry._format << ")\n";
+  }
+  MutexHolder holder(_lock);
+
+  EntriesIndex::iterator ii = _entries_index.find(&entry);
+  if (ii != _entries_index.end()) {
+    Entries::iterator ei = (*ii).second;
+    _total_size -= (*ei)._result_size;
+    _entries_index.erase(ii);
+    _entries.erase(ei);
+  }
+}

+ 113 - 0
panda/src/gobj/qpgeomVertexCacheManager.h

@@ -0,0 +1,113 @@
+// Filename: qpgeomVertexCacheManager.h
+// Created by:  drose (11Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpGEOMVERTEXCACHEMANAGER_H
+#define qpGEOMVERTEXCACHEMANAGER_H
+
+#include "pandabase.h"
+#include "qpgeomMunger.h"
+#include "config_gobj.h"
+#include "pointerTo.h"
+#include "pmutex.h"
+#include "indirectLess.h"
+#include "plist.h"
+#include "pmap.h"
+
+class qpGeomPrimitive;
+class qpGeomVertexData;
+class qpGeomVertexFormat;
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpGeomVertexCacheManager
+// Description : This is used to keep track of, and limit the size of,
+//               the cache of munged vertices, which would otherwise
+//               be distributed through all of the GeomVertexData
+//               objects in the system.
+//
+//               The actual data in the cache is not stored here, but
+//               rather it is distributed among the various
+//               GeomVertexData source objects.  This allows the cache
+//               data to propagate through the multiprocess pipeline.
+//
+//               This is part of the experimental Geom rewrite.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpGeomVertexCacheManager {
+protected:
+  qpGeomVertexCacheManager();
+  ~qpGeomVertexCacheManager();
+
+PUBLISHED:
+  INLINE void set_max_size(int max_size) const;
+  INLINE int get_max_size() const;
+
+  INLINE int get_total_size() const;
+
+  static qpGeomVertexCacheManager *get_global_ptr();
+
+private:
+  INLINE void record_munger(const qpGeomMunger *munger);
+  INLINE void record_decompose(const qpGeomPrimitive *primitive,
+                               int result_size);
+  INLINE void remove_decompose(const qpGeomPrimitive *primitive);
+  INLINE void record_data(const qpGeomVertexData *source,
+                          const qpGeomVertexFormat *format,
+                          int result_size);
+  INLINE void remove_data(const qpGeomVertexData *source,
+                          const qpGeomVertexFormat *format);
+
+  class Entry;
+
+  void record_entry(const Entry &entry);
+  void remove_entry(const Entry &entry);
+
+private:
+  // This mutex protects all operations on this object.
+  Mutex _lock;
+
+  int _total_size;
+
+  class Entry {
+  public:
+    INLINE bool operator < (const Entry &other) const;
+
+    CPT(qpGeomMunger) _munger;
+    const qpGeomPrimitive *_primitive;
+    const qpGeomVertexData *_source;
+    const qpGeomVertexFormat *_format;
+    int _result_size;
+  };
+
+  // This list keeps the cache entries in least-recently-used order:
+  // the items at the head of the list are ready to be flushed.
+  typedef plist<Entry> Entries;
+  Entries _entries;
+
+  // And this indexes into the above list, for fast lookup.
+  typedef pmap<const Entry *, Entries::iterator, IndirectLess<Entry> > EntriesIndex;
+  EntriesIndex _entries_index;
+
+  static qpGeomVertexCacheManager *_global_ptr;
+
+  friend class qpGeomMunger;
+  friend class qpGeomPrimitive;
+  friend class qpGeomVertexData;
+};
+
+#include "qpgeomVertexCacheManager.I"
+
+#endif

+ 233 - 95
panda/src/gobj/qpgeomVertexData.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "qpgeomVertexData.h"
+#include "qpgeomVertexCacheManager.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "pset.h"
@@ -82,6 +83,25 @@ operator = (const qpGeomVertexData &copy) {
 ////////////////////////////////////////////////////////////////////
 qpGeomVertexData::
 ~qpGeomVertexData() {
+  // When we destruct, we should ensure that all of our cached
+  // entries, across all pipeline stages, are properly removed from
+  // the cache manager.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  int num_stages = _cycler.get_num_stages();
+  for (int i = 0; i < num_stages; i++) {
+    if (_cycler.is_stage_unique(i)) {
+      CData *cdata = _cycler.write_stage(i);
+      for (ConvertedCache::iterator ci = cdata->_converted_cache.begin();
+           ci != cdata->_converted_cache.end();
+           ++ci) {
+        cache_mgr->remove_data(this, (*ci).first);
+      }
+      cdata->_converted_cache.clear();
+      _cycler.release_write_stage(i, cdata);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -117,11 +137,14 @@ set_num_vertices(int n) {
   CDWriter cdata(_cycler);
   nassertv(_format->get_num_arrays() == (int)cdata->_arrays.size());
 
+  bool any_changed = false;
+
   for (size_t i = 0; i < cdata->_arrays.size(); i++) {
     int stride = _format->get_array(i)->get_stride();
     int delta = n - (cdata->_arrays[i].size() / stride);
 
     if (delta != 0) {
+      any_changed = true;
       if (cdata->_arrays[i].get_ref_count() > 1) {
         // Copy-on-write: the array is already reffed somewhere else,
         // so we're just going to make a copy.
@@ -145,6 +168,10 @@ set_num_vertices(int n) {
       }
     }
   }
+
+  if (any_changed) {
+    clear_cache();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -190,6 +217,8 @@ modify_array_data(int array) {
     cdata->_arrays[array] = PTA_uchar();
     cdata->_arrays[array].v() = orig_data.v();
   }
+
+  clear_cache();
   return cdata->_arrays[array];
 }
 
@@ -209,105 +238,23 @@ set_array_data(int array, PTA_uchar array_data) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexData::set_data
+//     Function: qpGeomVertexData::get_num_bytes
 //       Access: Published
-//  Description: Sets the nth vertex to a particular value.  Query the
-//               format to get the array index and data_type
-//               parameters for the particular data type you want to
-//               set.
-//
-//               This flavor of set_data() accepts a generic float
-//               array and a specific number of dimensions.  The new
-//               data will be copied from the num_values elements
-//               of data.
+//  Description: Returns the total number of bytes consumed by the
+//               different arrays of the vertex data.
 ////////////////////////////////////////////////////////////////////
-void qpGeomVertexData::
-set_data(int array, const qpGeomVertexDataType *data_type,
-         int vertex, const float *data, int num_values) {
-  int stride = _format->get_array(array)->get_stride();
-  int element = vertex * stride + data_type->get_start();
-
-  {
-    CDReader cdata(_cycler);
-    int array_size = (int)cdata->_arrays[array].size();
-    if (element + data_type->get_total_bytes() > array_size) {
-      // Whoops, we need more vertices!
-      set_num_vertices(vertex + 1);
-    }
-  }
-
-  PTA_uchar array_data = modify_array_data(array);
-  nassertv(element >= 0 && element + data_type->get_total_bytes() <= (int)array_data.size());
-
-  switch (data_type->get_numeric_type()) {
-  case qpGeomVertexDataType::NT_uint8:
-    {
-      nassertv(num_values <= data_type->get_num_values());
-      for (int i = 0; i < num_values; i++) {
-        int value = (int)(data[i] * 255.0f);
-        *(unsigned char *)&array_data[element] = value;
-        element += 1;
-      }
-    }
-    break;
-
-  case qpGeomVertexDataType::NT_packed_argb:
-    {
-      nassertv(num_values == 4);
-      *(PN_uint32 *)&array_data[element] = pack_argb(data);
-    }
-    break;
+int qpGeomVertexData::
+get_num_bytes() const {
+  CDReader cdata(_cycler);
+  
+  int num_bytes = 0;
 
-  case qpGeomVertexDataType::NT_float:
-    nassertv(num_values == data_type->get_num_values());
-    memcpy(&array_data[element], data, data_type->get_total_bytes());
-    break;
+  Arrays::const_iterator ai;
+  for (ai = cdata->_arrays.begin(); ai != cdata->_arrays.end(); ++ai) {
+    num_bytes += (*ai).size();
   }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexData::get_data
-//       Access: Published
-//  Description: Returns the data associated with the nth vertex for a
-//               particular value.  Query the format to get the array
-//               index and data_type parameters for the particular
-//               data type you want to get.
-//
-//               This flavor of get_data() copies its data into a
-//               generic float array.
-////////////////////////////////////////////////////////////////////
-void qpGeomVertexData::
-get_data(int array, const qpGeomVertexDataType *data_type,
-         int vertex, float *data, int num_values) const {
-  CPTA_uchar array_data = get_array_data(array);
-  int stride = _format->get_array(array)->get_stride();
-  int element = vertex * stride + data_type->get_start();
-  nassertv(element >= 0 && element + data_type->get_total_bytes() <= (int)array_data.size());
-
-  switch (data_type->get_numeric_type()) {
-  case qpGeomVertexDataType::NT_uint8:
-    {
-      nassertv(num_values <= data_type->get_num_values());
-      for (int i = 0; i < num_values; i++) {
-        int value = *(unsigned char *)&array_data[element];
-        element += 1;
-        data[i] = (float)value / 255.0f;
-      }
-    }
-    break;
 
-  case qpGeomVertexDataType::NT_packed_argb:
-    {
-      nassertv(num_values == 4);
-      unpack_argb(data, *(PN_uint32 *)&array_data[element]);
-    }
-    break;
-
-  case qpGeomVertexDataType::NT_float:
-    nassertv(num_values <= data_type->get_num_values());
-    memcpy(data, &array_data[element], num_values * sizeof(PN_float32));
-    break;
-  }
+  return num_bytes;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -318,10 +265,43 @@ get_data(int array, const qpGeomVertexDataType *data_type,
 //               the data vertex-by-vertex to a new set of data arrays
 //               in the new format.
 ////////////////////////////////////////////////////////////////////
-PT(qpGeomVertexData) qpGeomVertexData::
+CPT(qpGeomVertexData) qpGeomVertexData::
 convert_to(const qpGeomVertexFormat *new_format) const {
+  if (new_format == _format) {
+    // Trivial case: no change is needed.
+    return this;
+  }
+
+  // Look up the format in our cache--maybe we've recently converted
+  // to the requested format.
+  {
+    // Use read() and release_read() instead of CDReader, because the
+    // call to record_data() might recursively call back into this
+    // object, and require a write.
+    const CData *cdata = _cycler.read();
+    ConvertedCache::const_iterator ci = 
+      cdata->_converted_cache.find(new_format);
+    if (ci != cdata->_converted_cache.end()) {
+      _cycler.release_read(cdata);
+      // Record a cache hit, so this element will stay in the cache a
+      // while longer.
+      qpGeomVertexCacheManager *cache_mgr = 
+        qpGeomVertexCacheManager::get_global_ptr();
+      cache_mgr->record_data(this, new_format, (*ci).second->get_num_bytes());
+
+      return (*ci).second;
+    }
+    _cycler.release_read(cdata);
+  }
+
+  // Okay, convert the data to the new format.
   int num_vertices = get_num_vertices();
 
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Converting " << num_vertices << " vertices.\n";
+  }
+
   PT(qpGeomVertexData) new_data = new qpGeomVertexData(new_format);
 
   pset<int> done_arrays;
@@ -383,6 +363,19 @@ convert_to(const qpGeomVertexFormat *new_format) const {
     }
   }
 
+  // Record the new result in the cache.
+  {
+    CDWriter cdata(((qpGeomVertexData *)this)->_cycler);
+    cdata->_converted_cache[new_format] = new_data;
+  }
+
+  // And tell the cache manager about the new entry.  (It might
+  // immediately request a delete from the cache of the thing we just
+  // added.)
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+  cache_mgr->record_data(this, new_format, new_data->get_num_bytes());
+
   return new_data;
 }
 
@@ -406,6 +399,130 @@ write(ostream &out, int indent_level) const {
   _format->write_with_data(out, indent_level, this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::clear_cache
+//       Access: Published
+//  Description: Removes all of the previously-cached results of
+//               convert_to().
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+clear_cache() {
+  // Probably we shouldn't do anything at all here unless we are
+  // running in pipeline stage 0.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  CData *cdata = CDWriter(_cycler);
+  for (ConvertedCache::iterator ci = cdata->_converted_cache.begin();
+       ci != cdata->_converted_cache.end();
+       ++ci) {
+    cache_mgr->remove_data(this, (*ci).first);
+  }
+  cdata->_converted_cache.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::set_data
+//       Access: Public
+//  Description: Sets the nth vertex to a particular value.  Query the
+//               format to get the array index and data_type
+//               parameters for the particular data type you want to
+//               set.
+//
+//               This flavor of set_data() accepts a generic float
+//               array and a specific number of dimensions.  The new
+//               data will be copied from the num_values elements
+//               of data.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+set_data(int array, const qpGeomVertexDataType *data_type,
+         int vertex, const float *data, int num_values) {
+  int stride = _format->get_array(array)->get_stride();
+  int element = vertex * stride + data_type->get_start();
+
+  {
+    CDReader cdata(_cycler);
+    int array_size = (int)cdata->_arrays[array].size();
+    if (element + data_type->get_total_bytes() > array_size) {
+      // Whoops, we need more vertices!
+      set_num_vertices(vertex + 1);
+    }
+  }
+
+  PTA_uchar array_data = modify_array_data(array);
+  nassertv(element >= 0 && element + data_type->get_total_bytes() <= (int)array_data.size());
+
+  switch (data_type->get_numeric_type()) {
+  case qpGeomVertexDataType::NT_uint8:
+    {
+      nassertv(num_values <= data_type->get_num_values());
+      for (int i = 0; i < num_values; i++) {
+        int value = (int)(data[i] * 255.0f);
+        *(unsigned char *)&array_data[element] = value;
+        element += 1;
+      }
+    }
+    break;
+
+  case qpGeomVertexDataType::NT_packed_argb:
+    {
+      nassertv(num_values == 4);
+      *(PN_uint32 *)&array_data[element] = pack_argb(data);
+    }
+    break;
+
+  case qpGeomVertexDataType::NT_float:
+    nassertv(num_values == data_type->get_num_values());
+    memcpy(&array_data[element], data, data_type->get_total_bytes());
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::get_data
+//       Access: Public
+//  Description: Returns the data associated with the nth vertex for a
+//               particular value.  Query the format to get the array
+//               index and data_type parameters for the particular
+//               data type you want to get.
+//
+//               This flavor of get_data() copies its data into a
+//               generic float array.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+get_data(int array, const qpGeomVertexDataType *data_type,
+         int vertex, float *data, int num_values) const {
+  CPTA_uchar array_data = get_array_data(array);
+  int stride = _format->get_array(array)->get_stride();
+  int element = vertex * stride + data_type->get_start();
+  nassertv(element >= 0 && element + data_type->get_total_bytes() <= (int)array_data.size());
+
+  switch (data_type->get_numeric_type()) {
+  case qpGeomVertexDataType::NT_uint8:
+    {
+      nassertv(num_values <= data_type->get_num_values());
+      for (int i = 0; i < num_values; i++) {
+        int value = *(unsigned char *)&array_data[element];
+        element += 1;
+        data[i] = (float)value / 255.0f;
+      }
+    }
+    break;
+
+  case qpGeomVertexDataType::NT_packed_argb:
+    {
+      nassertv(num_values == 4);
+      unpack_argb(data, *(PN_uint32 *)&array_data[element]);
+    }
+    break;
+
+  case qpGeomVertexDataType::NT_float:
+    nassertv(num_values <= data_type->get_num_values());
+    memcpy(data, &array_data[element], num_values * sizeof(PN_float32));
+    break;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::get_array_info
 //       Access: Public
@@ -546,6 +663,27 @@ unpack_argb(float data[4], unsigned int packed_argb) {
   data[3] = (float)((packed_argb >> 24) & 0xff) / 255.0f;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::remove_cache_entry
+//       Access: Private
+//  Description: Removes a particular entry from the local cache; it
+//               has already been removed from the cache manager.
+//               This is only called from GeomVertexCacheManager.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+remove_cache_entry(const qpGeomVertexFormat *format) const {
+  // We have to operate on stage 0 of the pipeline, since that's where
+  // the cache really counts.  Because of the multistage pipeline, we
+  // might not actually have a cache entry there (it might have been
+  // added to stage 1 instead).  No big deal if we don't.
+  CData *cdata = ((qpGeomVertexData *)this)->_cycler.write_stage(0);
+  ConvertedCache::iterator ci = cdata->_converted_cache.find(format);
+  if (ci != cdata->_converted_cache.end()) {
+    cdata->_converted_cache.erase(ci);
+  }
+  ((qpGeomVertexData *)this)->_cycler.release_write_stage(0, cdata);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::register_with_read_factory
 //       Access: Public, Static

+ 23 - 6
panda/src/gobj/qpgeomVertexData.h

@@ -77,17 +77,21 @@ PUBLISHED:
   PTA_uchar modify_array_data(int array);
   void set_array_data(int array, PTA_uchar array_data);
 
-  void set_data(int array, const qpGeomVertexDataType *data_type,
-                int vertex, const float *data, int num_values);
-  void get_data(int array, const qpGeomVertexDataType *data_type,
-                int vertex, float *data, int num_values) const;
+  int get_num_bytes() const;
 
-  PT(qpGeomVertexData) convert_to(const qpGeomVertexFormat *new_format) const;
+  CPT(qpGeomVertexData) convert_to(const qpGeomVertexFormat *new_format) const;
 
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
 
+  void clear_cache();
+
 public:
+  void set_data(int array, const qpGeomVertexDataType *data_type,
+                int vertex, const float *data, int num_values);
+  void get_data(int array, const qpGeomVertexDataType *data_type,
+                int vertex, float *data, int num_values) const;
+
   bool get_array_info(const InternalName *name, CPTA_uchar &array_data,
                       int &num_components,
                       qpGeomVertexDataType::NumericType &numeric_type, 
@@ -101,10 +105,20 @@ public:
   static void unpack_argb(float data[4], unsigned int packed_argb);
 
 private:
-  typedef pvector<PTA_uchar> Arrays;
+  void remove_cache_entry(const qpGeomVertexFormat *format) const;
 
+private:
   CPT(qpGeomVertexFormat) _format;
 
+  typedef pvector<PTA_uchar> Arrays;
+
+  // We have to use reference-counting pointers here instead of having
+  // explicit cleanup in the GeomVertexFormat destructor, because the
+  // cache needs to be stored in the CycleData, which makes accurate
+  // cleanup more difficult.  We use the GeomVertexCacheManager class
+  // to avoid cache bloat.
+  typedef pmap<CPT(qpGeomVertexFormat), CPT(qpGeomVertexData) > ConvertedCache;
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   public:
@@ -116,6 +130,7 @@ private:
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
 
     Arrays _arrays;
+    ConvertedCache _converted_cache;
   };
 
   PipelineCycler<CData> _cycler;
@@ -146,6 +161,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class qpGeomVertexCacheManager;
 };
 
 INLINE ostream &operator << (ostream &out, const qpGeomVertexData &obj);

+ 2 - 0
panda/src/gobj/qpgeomVertexFormat.cxx

@@ -19,6 +19,7 @@
 #include "qpgeomVertexFormat.h"
 #include "qpgeomVertexData.h"
 #include "qpgeomMunger.h"
+#include "mutexHolder.h"
 #include "indent.h"
 #include "bamReader.h"
 #include "bamWriter.h"
@@ -435,6 +436,7 @@ do_unregister() {
 
   _data_types_by_name.clear();
 
+  MutexHolder holder(_cache_lock);
   Mungers::iterator mi;
   for (mi = _mungers.begin(); mi != _mungers.end(); ++mi) {
     (*mi)->remove_format(this);

+ 5 - 0
panda/src/gobj/qpgeomVertexFormat.h

@@ -30,6 +30,7 @@
 #include "pvector.h"
 #include "pta_float.h"
 #include "indirectCompareTo.h"
+#include "pmutex.h"
 
 class FactoryParams;
 class qpGeomVertexData;
@@ -142,9 +143,13 @@ private:
   typedef pmap<const InternalName *, DataTypeRecord> DataTypesByName;
   DataTypesByName _data_types_by_name;
 
+  // This set keeps track of things that need to be told when we
+  // destruct, and it is protected by the mutex.
+  Mutex _cache_lock;
   typedef pset<qpGeomMunger *> Mungers;
   Mungers _mungers;
 
+  // This is the global registry of all currently-in-use formats.
   typedef pset<qpGeomVertexFormat *, IndirectCompareTo<qpGeomVertexFormat> > Formats;
   class Registry {
   public:

+ 3 - 0
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -47,6 +47,7 @@ class qpGeomVertexData;
 class qpGeomTriangles;
 class qpGeomTristrips;
 class qpGeomTrifans;
+class qpGeomMunger;
 
 class PreparedGraphicsObjects;
 class GraphicsOutput;
@@ -130,6 +131,8 @@ public:
   virtual GeomContext *prepare_geom(Geom *geom)=0;
   virtual void release_geom(GeomContext *gc)=0;
 
+  virtual CPT(qpGeomMunger) get_geom_munger(const RenderState *state)=0;
+
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform)=0;
 

+ 1 - 1
panda/src/pgraph/cullHandler.I

@@ -30,6 +30,6 @@ draw(CullableObject *object, GraphicsStateGuardianBase *gsg) {
     draw_with_decals(object, gsg);
   } else {
     gsg->set_state_and_transform(object->_state, object->_transform);
-    object->_geom->draw(gsg);
+    object->draw(gsg);
   }
 }

+ 3 - 3
panda/src/pgraph/cullHandler.cxx

@@ -66,7 +66,7 @@ draw_with_decals(CullableObject *object, GraphicsStateGuardianBase *gsg) {
   CullableObject *base = object;
   while (base != (CullableObject *)NULL && base->_geom != (Geom *)NULL) {
     gsg->set_state_and_transform(base->_state->compose(state), base->_transform);
-    base->_geom->draw(gsg);
+    base->draw(gsg);
     
     base = base->_next;
   }
@@ -78,7 +78,7 @@ draw_with_decals(CullableObject *object, GraphicsStateGuardianBase *gsg) {
     CullableObject *decal = base->_next;
     while (decal != (CullableObject *)NULL) {
       gsg->set_state_and_transform(decal->_state->compose(state), decal->_transform);
-      decal->_geom->draw(gsg);
+      decal->draw(gsg);
       decal = decal->_next;
     }
   }
@@ -89,7 +89,7 @@ draw_with_decals(CullableObject *object, GraphicsStateGuardianBase *gsg) {
     base = object;
     while (base != (CullableObject *)NULL && base->_geom != (Geom *)NULL) {
       gsg->set_state_and_transform(base->_state->compose(state), base->_transform);
-      base->_geom->draw(gsg);
+      base->draw(gsg);
       
       base = base->_next;
     }

+ 4 - 0
panda/src/pgraph/cullResult.cxx

@@ -85,6 +85,10 @@ make_next() const {
 ////////////////////////////////////////////////////////////////////
 void CullResult::
 add_object(CullableObject *object) {
+  // Munge vertices as needed for the GSG's requirements, and the
+  // object's current state.
+  object->munge_vertices(_gsg);
+
   // Check to see if there's a special transparency setting.
   const RenderState *state = object->_state;
   nassertv(state != (const RenderState *)NULL);

+ 29 - 0
panda/src/pgraph/cullableObject.I

@@ -103,6 +103,35 @@ has_decals() const {
   return (_next != (CullableObject *)NULL);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CullableObject::munge_vertices
+//       Access: Public
+//  Description: Gets a GeomMunger from the GSG to transform the
+//               vertices to meet the GSG's vertex requirements for
+//               the current state.
+////////////////////////////////////////////////////////////////////
+INLINE void CullableObject::
+munge_vertices(GraphicsStateGuardianBase *gsg) {
+  // Temporary test until the experimental Geom rewrite becomes the
+  // actual Geom implementation.
+  if (_geom->is_exact_type(qpGeom::get_class_type())) {
+    CPT(qpGeomMunger) munger = gsg->get_geom_munger(_state);
+    _munged_data = munger->munge_data(((const qpGeom *)_geom.p())->get_vertex_data());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullableObject::draw
+//       Access: Public
+//  Description: Draws the cullable object on the GSG immediately, in
+//               the GSG's current state.  This should only be called
+//               from the draw thread.
+////////////////////////////////////////////////////////////////////
+INLINE void CullableObject::
+draw(GraphicsStateGuardianBase *gsg) {
+  _geom->draw(gsg, _munged_data);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CullableObject::operator new
 //       Access: Public

+ 7 - 0
panda/src/pgraph/cullableObject.h

@@ -22,6 +22,9 @@
 #include "pandabase.h"
 
 #include "geom.h"
+#include "qpgeom.h"
+#include "qpgeomVertexData.h"
+#include "qpgeomMunger.h"
 #include "renderState.h"
 #include "transformState.h"
 #include "pointerTo.h"
@@ -51,6 +54,9 @@ public:
 
   INLINE bool has_decals() const;
 
+  INLINE void munge_vertices(GraphicsStateGuardianBase *gsg);
+  INLINE void draw(GraphicsStateGuardianBase *gsg);
+
 public:
   ~CullableObject();
 
@@ -67,6 +73,7 @@ PUBLISHED:
 
 public:
   CPT(Geom) _geom;
+  CPT(qpGeomVertexData) _munged_data;
   CPT(RenderState) _state;
   CPT(TransformState) _transform;
   CullableObject *_next;

+ 5 - 0
panda/src/pgraph/drawCullHandler.cxx

@@ -33,6 +33,11 @@
 ////////////////////////////////////////////////////////////////////
 void DrawCullHandler::
 record_object(CullableObject *object) {
+  // Munge vertices as needed for the GSG's requirements, and the
+  // object's current state.
+  object->munge_vertices(_gsg);
+
+  // And draw the object, then dispense with it.
   draw(object, _gsg);
   delete object;
 }

+ 1 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -125,6 +125,7 @@ static TimeCollectorProperties time_properties[] = {
   { 1, "Cull",                             { 0.0, 1.0, 0.0 },  1.0 / 30.0 },
   { 1, "Cull:Show fps",                    { 0.5, 0.8, 1.0 } },
   { 1, "Cull:Bins",                        { 0.3, 0.6, 0.3 } },
+  { 1, "Cull:Munge",                       { 0.3, 0.3, 0.9 } },
   { 1, "Draw",                             { 1.0, 0.0, 0.0 },  1.0 / 30.0 },
   { 1, "Draw:Make current",                { 0.4, 0.2, 0.6 } },
   { 1, "Draw:Copy texture",                { 0.2, 0.6, 0.4 } },