Browse Source

GeomVertexData::reserve_num_rows() and related changes--big performance improvement, especially on Windows

David Rose 14 years ago
parent
commit
372532346e

+ 21 - 1
panda/src/egg2pg/eggLoader.cxx

@@ -411,7 +411,14 @@ make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform,
       // Add each new primitive to the Geom.
       Primitives::const_iterator pi;
       for (pi = primitives.begin(); pi != primitives.end(); ++pi) {
-        GeomPrimitive *primitive = (*pi);
+        PT(GeomPrimitive) primitive = (*pi);
+
+        if (primitive->is_indexed()) {
+          // Since we may have over-allocated while we were filling up
+          // the primitives, down-allocate now.
+          primitive->reserve_num_vertices(primitive->get_num_vertices());
+        }
+
         geom->add_primitive(primitive);
       }
       
@@ -2313,6 +2320,7 @@ make_vertex_data(const EggRenderState *render_state,
   // dynamic.
   PT(GeomVertexData) vertex_data =
     new GeomVertexData(name, format, Geom::UH_static);
+  vertex_data->reserve_num_rows(vertex_pool->size());
 
   vertex_data->set_transform_blend_table(blend_table);
   if (slider_table != (SliderTable *)NULL) {
@@ -2543,6 +2551,12 @@ make_primitive(const EggRenderState *render_state, EggPrimitive *egg_prim,
   if (result.second) {
     // This was the first primitive of this type.  Store it.
     primitives.push_back(primitive);
+
+    if (egg2pg_cat.is_debug()) {
+      egg2pg_cat.debug()
+        << "First primitive of type " << primitive->get_type() 
+        << ": " << primitive << "\n";
+    }
   }
 
   GeomPrimitive *orig_prim = (*result.first).second;
@@ -2556,6 +2570,12 @@ make_primitive(const EggRenderState *render_state, EggPrimitive *egg_prim,
     // If the old primitive is full, keep the new primitive from now
     // on.
     (*result.first).second = primitive;
+
+    if (egg2pg_cat.is_debug()) {
+      egg2pg_cat.debug()
+        << "Next primitive of type " << primitive->get_type() 
+        << ": " << primitive << "\n";
+    }
     primitives.push_back(primitive);
   }
 

+ 54 - 0
panda/src/gobj/geomPrimitive.cxx

@@ -188,6 +188,11 @@ void GeomPrimitive::
 add_vertex(int vertex) {
   CDWriter cdata(_cycler, true);
 
+  if (gobj_cat.is_spam()) {
+    gobj_cat.spam()
+      << this << ".add_vertex(" << vertex << ")\n";
+  }
+
   consider_elevate_index_type(cdata, vertex);
 
   int num_primitives = get_num_primitives();
@@ -320,6 +325,42 @@ add_next_vertices(int num_vertices) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::reserve_num_vertices
+//       Access: Published
+//  Description: This ensures that enough memory space for n vertices
+//               is allocated, so that you may increase the number of
+//               vertices to n without causing a new memory
+//               allocation.  This is a performance optimization only;
+//               it is especially useful when you know ahead of time
+//               that you will be adding n vertices to the primitive.
+//
+//               Note that the total you specify here should also
+//               include implicit vertices which may be added at each
+//               close_primitive() call, according to
+//               get_num_unused_vertices_per_primitive().
+//
+//               Note also that making this call will implicitly make
+//               the primitive indexed if it is not already, which
+//               could result in a performance *penalty*.  If you
+//               would prefer not to lose the nonindexed nature of
+//               your existing GeomPrimitives, check is_indexed()
+//               before making this call.
+////////////////////////////////////////////////////////////////////
+void GeomPrimitive::
+reserve_num_vertices(int num_vertices) {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << this << ".reserve_num_vertices(" << num_vertices << ")\n";
+  }
+
+  CDWriter cdata(_cycler, true);
+  consider_elevate_index_type(cdata, num_vertices);
+  do_make_indexed(cdata);
+  PT(GeomVertexArrayData) array_obj = cdata->_vertices.get_write_pointer();
+  array_obj->reserve_num_rows(num_vertices);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::close_primitive
 //       Access: Published
@@ -1646,7 +1687,9 @@ recompute_minmax(GeomPrimitive::CData *cdata) {
       cdata->_maxs = make_index_data();
       
       GeomVertexWriter mins(cdata->_mins.get_write_pointer(), 0);
+      mins.reserve_num_rows(cdata->_ends.size());
       GeomVertexWriter maxs(cdata->_maxs.get_write_pointer(), 0);
+      maxs.reserve_num_rows(cdata->_ends.size());
       
       int pi = 0;
       
@@ -1711,9 +1754,15 @@ recompute_minmax(GeomPrimitive::CData *cdata) {
 void GeomPrimitive::
 do_make_indexed(CData *cdata) {
   if (cdata->_vertices.is_null()) {
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << this << ".make_indexed()\n";
+    }
+
     nassertv(cdata->_num_vertices != -1);
     cdata->_vertices = make_index_data();
     GeomVertexWriter index(cdata->_vertices.get_write_pointer(), 0);
+    index.reserve_num_rows(cdata->_num_vertices);
     for (int i = 0; i < cdata->_num_vertices; ++i) {
       index.add_data1i(i + cdata->_first_vertex);
     }
@@ -1762,6 +1811,11 @@ void GeomPrimitive::
 do_set_index_type(CData *cdata, GeomPrimitive::NumericType index_type) {
   cdata->_index_type = index_type;
 
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << this << ".set_index_type(" << index_type << ")\n";
+  }
+
   if (!cdata->_vertices.is_null()) {
     CPT(GeomVertexArrayFormat) new_format = get_index_format();
     

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

@@ -105,6 +105,7 @@ PUBLISHED:
   INLINE void add_vertices(int v1, int v2, int v3, int v4);
   void add_consecutive_vertices(int start, int num_vertices);
   void add_next_vertices(int num_vertices);
+  void reserve_num_vertices(int num_vertices);
   bool close_primitive();
   void clear_vertices();
   void offset_vertices(int offset);

+ 15 - 0
panda/src/gobj/geomVertexArrayData.I

@@ -106,6 +106,21 @@ unclean_set_num_rows(int n) {
   return modify_handle()->unclean_set_num_rows(n);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexArrayData::reserve_num_rows
+//       Access: Published
+//  Description: This ensures that enough memory space for n rows is
+//               allocated, so that you may increase the number of
+//               rows to n without causing a new memory allocation.
+//               This is a performance optimization only; it is
+//               especially useful when you know ahead of time that
+//               you will be adding n rows to the data.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeomVertexArrayData::
+reserve_num_rows(int n) {
+  return modify_handle()->reserve_num_rows(n);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexArrayData::clear_rows
 //       Access: Published

+ 96 - 11
panda/src/gobj/geomVertexArrayData.cxx

@@ -24,6 +24,7 @@
 #include "configVariableInt.h"
 #include "simpleAllocator.h"
 #include "vertexDataBuffer.h"
+#include "texture.h"
 
 ConfigVariableInt max_independent_vertex_data
 ("max-independent-vertex-data", -1,
@@ -641,12 +642,14 @@ fillin(DatagramIterator &scan, BamReader *manager, void *extra_data) {
     PTA_uchar new_data;
     READ_PTA(manager, scan, array_data->read_raw_data, new_data);
     _buffer.unclean_realloc(new_data.size());
+    _buffer.set_size(new_data.size());
     memcpy(_buffer.get_write_pointer(), &new_data[0], new_data.size());
 
   } else {
     // Now, the array data is just stored directly.
     size_t size = scan.get_uint32();
     _buffer.unclean_realloc(size);
+    _buffer.set_size(size);
 
     const unsigned char *source_data = 
       (const unsigned char *)scan.get_datagram().get_data();
@@ -712,8 +715,29 @@ set_num_rows(int n) {
   size_t new_size = n * stride;
   size_t orig_size = _cdata->_buffer.get_size();
 
+  if (gobj_cat.is_spam()) {
+    gobj_cat.spam()
+      << _object << ".set_num_rows(" << n << "), size = " << new_size << "\n";
+  }
+
   if (new_size != orig_size) {
-    _cdata->_buffer.clean_realloc(new_size);
+    size_t orig_reserved_size = _cdata->_buffer.get_reserved_size();
+    if (new_size > orig_reserved_size) {
+      // Add more rows.  Go up to the next power of two bytes, mainly
+      // to reduce the number of allocs needed.
+      size_t new_reserved_size = (size_t)Texture::up_to_power_2((int)new_size);
+      nassertr(new_reserved_size >= new_size, false);
+
+      _cdata->_buffer.clean_realloc(new_reserved_size);
+
+    } else if (new_size == 0) {
+      // If we set the number of rows to 0, go ahead and clear the
+      // buffer altogether, and let the user build it up again from
+      // nothing, to try to reduce frivolous memory waste.
+      _cdata->_buffer.clear();
+    }
+
+    _cdata->_buffer.set_size(new_size);
 
     // Now ensure that the newly-added rows are initialized to 0.
     if (new_size > orig_size) {
@@ -726,9 +750,12 @@ set_num_rows(int n) {
     if (get_current_thread()->get_pipeline_stage() == 0) {
       _object->set_lru_size(_cdata->_buffer.get_size());
     }
+
+    nassertr(get_num_rows() == n, true);
     return true;
   }
   
+  nassertr(get_num_rows() == n, false);
   return false;
 }
 
@@ -745,16 +772,26 @@ unclean_set_num_rows(int n) {
   int stride = _object->_array_format->get_stride();
   size_t new_size = n * stride;
   size_t orig_size = _cdata->_buffer.get_size();
+  size_t orig_reserved_size = _cdata->_buffer.get_reserved_size();
+
+  if (new_size != orig_size || new_size != orig_reserved_size) {
+    // Since this is unclean_set_num_rows(), we won't be using it to
+    // incrementally increase the array; instead, it will generally be
+    // used only to create an array initially.  So it makes sense to
+    // set the reserved size to precisely the same as the target size.
 
-  if (new_size != orig_size) {
     _cdata->_buffer.unclean_realloc(new_size);
+    _cdata->_buffer.set_size(new_size);
+
     // No need to fill to zero or copy the old buffer, since this is
     // unclean_set_num_rows().
 
-    _cdata->_modified = Geom::get_next_modified();
+    if (new_size != orig_size) {
+      _cdata->_modified = Geom::get_next_modified();
 
-    if (get_current_thread()->get_pipeline_stage() == 0) {
-      _object->set_lru_size(_cdata->_buffer.get_size());
+      if (get_current_thread()->get_pipeline_stage() == 0) {
+        _object->set_lru_size(_cdata->_buffer.get_size());
+      }
     }
     return true;
   }
@@ -762,6 +799,38 @@ unclean_set_num_rows(int n) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexArrayDataHandle::reserve_num_rows
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool GeomVertexArrayDataHandle::
+reserve_num_rows(int n) {
+  nassertr(_writable, false);
+  mark_used();
+
+  int stride = _object->_array_format->get_stride();
+  size_t new_reserved_size = n * stride;
+  new_reserved_size = max(_cdata->_buffer.get_size(), new_reserved_size);
+  size_t orig_reserved_size = _cdata->_buffer.get_reserved_size();
+
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << _object << ".reserve_num_rows(" << n << "), size = " << new_reserved_size << "\n";
+  }
+
+  if (new_reserved_size != orig_reserved_size) {
+    // We allow the user to set the alloc point smaller with this
+    // call, assuming the user knows what he's doing.  This allows the
+    // user to reduce wasted memory after completely filling up a
+    // buffer.
+    _cdata->_buffer.clean_realloc(new_reserved_size);
+    return true;
+  }
+
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexArrayDataHandle::copy_data_from
 //       Access: Public
@@ -773,11 +842,12 @@ copy_data_from(const GeomVertexArrayDataHandle *other) {
   mark_used();
   other->mark_used();
 
-  _cdata->_buffer.unclean_realloc(other->_cdata->_buffer.get_size());
+  size_t size = other->_cdata->_buffer.get_size();
+  _cdata->_buffer.unclean_realloc(size);
+  _cdata->_buffer.set_size(size);
 
   unsigned char *dest = _cdata->_buffer.get_write_pointer();
   const unsigned char *source = other->_cdata->_buffer.get_read_pointer(true);
-  size_t size = other->_cdata->_buffer.get_size();
   memcpy(dest, source, size);
 
   _cdata->_modified = Geom::get_next_modified();
@@ -819,11 +889,18 @@ copy_subdata_from(size_t to_start, size_t to_size,
     memmove(pointer + to_start + to_size, 
             pointer + to_start + from_size,
             to_buffer_orig_size - (to_start + to_size));
-    to_buffer.clean_realloc(to_buffer_orig_size + from_size - to_size);
+    to_buffer.set_size(to_buffer_orig_size + from_size - to_size);
 
   } else if (to_size < from_size) {
     // Expand the array.
-    to_buffer.clean_realloc(to_buffer_orig_size + from_size - to_size);
+    size_t needed_size = to_buffer_orig_size + from_size - to_size;
+    size_t to_buffer_orig_reserved_size = to_buffer.get_reserved_size();
+    if (needed_size > to_buffer_orig_reserved_size) {
+      size_t new_reserved_size = (size_t)Texture::up_to_power_2((int)needed_size);
+      to_buffer.clean_realloc(new_reserved_size);
+    }
+    to_buffer.set_size(needed_size);
+
     unsigned char *pointer = to_buffer.get_write_pointer();
     memmove(pointer + to_start + to_size, 
             pointer + to_start + from_size,
@@ -854,6 +931,7 @@ set_data(const string &data) {
   mark_used();
 
   _cdata->_buffer.unclean_realloc(data.size());
+  _cdata->_buffer.set_size(data.size());
   memcpy(_cdata->_buffer.get_write_pointer(), data.data(), data.size());
 
   _cdata->_modified = Geom::get_next_modified();
@@ -891,11 +969,18 @@ set_subdata(size_t start, size_t size, const string &data) {
     memmove(pointer + start + from_size, 
             pointer + start + size,
             to_buffer_orig_size - (start + size));
-    to_buffer.clean_realloc(to_buffer_orig_size + from_size - size);
+    to_buffer.set_size(to_buffer_orig_size + from_size - size);
 
   } else if (size < from_size) {
     // Expand the array.
-    to_buffer.clean_realloc(to_buffer_orig_size + from_size - size);
+    size_t needed_size = to_buffer_orig_size + from_size - size;
+    size_t to_buffer_orig_reserved_size = to_buffer.get_reserved_size();
+    if (needed_size > to_buffer_orig_reserved_size) {
+      size_t new_reserved_size = (size_t)Texture::up_to_power_2((int)needed_size);
+      to_buffer.clean_realloc(new_reserved_size);
+    }
+    to_buffer.set_size(needed_size);
+
     unsigned char *pointer = to_buffer.get_write_pointer();
     memmove(pointer + start + from_size, 
             pointer + start + size,

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

@@ -87,6 +87,7 @@ PUBLISHED:
   INLINE int get_num_rows() const;
   INLINE bool set_num_rows(int n);
   INLINE bool unclean_set_num_rows(int n);
+  INLINE bool reserve_num_rows(int n);
   INLINE void clear_rows();
 
   INLINE int get_data_size_bytes() const;
@@ -274,6 +275,7 @@ PUBLISHED:
   INLINE int get_num_rows() const;
   bool set_num_rows(int n);
   bool unclean_set_num_rows(int n);
+  bool reserve_num_rows(int n);
   INLINE void clear_rows();
 
   INLINE int get_data_size_bytes() const;

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

@@ -143,6 +143,23 @@ unclean_set_num_rows(int n) {
   return writer.unclean_set_num_rows(n);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::reserve_num_rows
+//       Access: Published
+//  Description: This ensures that enough memory space for n rows is
+//               allocated, so that you may increase the number of
+//               rows to n without causing a new memory allocation.
+//               This is a performance optimization only; it is
+//               especially useful when you know ahead of time that
+//               you will be adding n rows to the data.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeomVertexData::
+reserve_num_rows(int n) {
+  GeomVertexDataPipelineWriter writer(this, true, Thread::get_current_thread());
+  writer.check_array_writers();
+  return writer.reserve_num_rows(n);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::get_num_arrays
 //       Access: Published

+ 25 - 2
panda/src/gobj/geomVertexData.cxx

@@ -542,6 +542,7 @@ copy_from(const GeomVertexData *source, bool keep_data_objects,
   }
 
   // Now make sure the arrays we didn't share are all filled in.
+  reserve_num_rows(num_rows);
   set_num_rows(num_rows);
 
   // Now go back through and copy any data that's left over.
@@ -2273,8 +2274,9 @@ unclean_set_num_rows(int n) {
 
   for (size_t i = 0; i < _cdata->_arrays.size(); i++) {
     if (_array_writers[i]->get_num_rows() != n) {
-      _array_writers[i]->unclean_set_num_rows(n);
-      any_changed = true;
+      if (_array_writers[i]->unclean_set_num_rows(n)) {
+        any_changed = true;
+      }
     }
   }
 
@@ -2287,6 +2289,27 @@ unclean_set_num_rows(int n) {
   return any_changed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexDataPipelineWriter::reserve_num_rows
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool GeomVertexDataPipelineWriter::
+reserve_num_rows(int n) {
+  nassertr(_got_array_writers, false);
+  nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), false);
+
+  bool any_changed = false;
+
+  for (size_t i = 0; i < _cdata->_arrays.size(); i++) {
+    if (_array_writers[i]->reserve_num_rows(n)) {
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexDataPipelineWriter::modify_array
 //       Access: Public

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

@@ -105,6 +105,7 @@ PUBLISHED:
   INLINE int get_num_rows() const;
   INLINE bool set_num_rows(int n);
   INLINE bool unclean_set_num_rows(int n);
+  INLINE bool reserve_num_rows(int n);
   void clear_rows();
 
   INLINE int get_num_arrays() const;
@@ -487,6 +488,7 @@ public:
   int get_num_rows() const;
   bool set_num_rows(int n);
   bool unclean_set_num_rows(int n);
+  bool reserve_num_rows(int n);
 
 private:
   void make_array_writers();

+ 31 - 0
panda/src/gobj/geomVertexWriter.cxx

@@ -65,6 +65,37 @@ set_column(int array, const GeomVertexColumn *column) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexWriter::reserve_num_rows
+//       Access: Published
+//  Description: This ensures that enough memory space for num_rows is
+//               allocated, so that you may add up to num_rows rows
+//               without causing a new memory allocation.  This is a
+//               performance optimization only; it is especially
+//               useful when you know the number of rows you will be
+//               adding ahead of time.
+////////////////////////////////////////////////////////////////////
+bool GeomVertexWriter::
+reserve_num_rows(int num_rows) {
+  bool result;
+
+  if (_vertex_data != (GeomVertexData *)NULL) {
+    // If we have a whole GeomVertexData, we must set the length of
+    // all its arrays at once.
+    GeomVertexDataPipelineWriter writer(_vertex_data, true, _current_thread);
+    writer.check_array_writers();
+    result = writer.reserve_num_rows(num_rows);
+    _handle = writer.get_array_writer(_array);
+    
+  } else {
+    // Otherwise, we can get away with modifying only the one array
+    // we're using.
+    result = _handle->reserve_num_rows(num_rows);
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexWriter::output
 //       Access: Published

+ 1 - 0
panda/src/gobj/geomVertexWriter.h

@@ -101,6 +101,7 @@ PUBLISHED:
   INLINE bool set_column(const InternalName *name);
   bool set_column(int array, const GeomVertexColumn *column);
   INLINE void clear();
+  bool reserve_num_rows(int num_rows);
 
   INLINE bool has_column() const;
   INLINE int get_array() const;

+ 64 - 36
panda/src/gobj/vertexDataBuffer.I

@@ -21,7 +21,8 @@
 INLINE VertexDataBuffer::
 VertexDataBuffer() :
   _resident_data(NULL),
-  _size(0)
+  _size(0),
+  _reserved_size(0)
 {
 }
 
@@ -33,9 +34,11 @@ VertexDataBuffer() :
 INLINE VertexDataBuffer::
 VertexDataBuffer(size_t size) :
   _resident_data(NULL),
-  _size(0)
+  _size(0),
+  _reserved_size(0)
 {
   do_unclean_realloc(size);
+  _size = size;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -46,7 +49,8 @@ VertexDataBuffer(size_t size) :
 INLINE VertexDataBuffer::
 VertexDataBuffer(const VertexDataBuffer &copy) :
   _resident_data(NULL),
-  _size(0)
+  _size(0),
+  _reserved_size(0)
 {
   (*this) = copy;
 }
@@ -81,6 +85,7 @@ get_read_pointer(bool force) const {
   }
 
   nassertr(_block != (VertexDataBlock *)NULL, NULL);
+  nassertr(_reserved_size >= _size, NULL);
 
   // We don't necessarily need to page the buffer all the way into
   // independent status; it's sufficient just to return the block's
@@ -97,9 +102,10 @@ INLINE unsigned char *VertexDataBuffer::
 get_write_pointer() { 
   LightMutexHolder holder(_lock);
 
-  if (_resident_data == (unsigned char *)NULL && _size != 0) {
+  if (_resident_data == (unsigned char *)NULL && _reserved_size != 0) {
     do_page_in();
   }
+  nassertr(_reserved_size >= _size, NULL);
   return _resident_data;
 }
 
@@ -113,18 +119,59 @@ get_size() const {
   return _size;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VertexDataBuffer::get_reserved_size
+//       Access: Public
+//  Description: Returns the total number of bytes "reserved" in the
+//               buffer.  This may be greater than or equal to
+//               get_size().  If it is greater, the additional bytes
+//               are extra unused bytes in the buffer, and this
+//               indicates the maximum value that may be passed to
+//               set_size() without first calling one of the realloc
+//               methods.
+////////////////////////////////////////////////////////////////////
+INLINE size_t VertexDataBuffer::
+get_reserved_size() const {
+  return _reserved_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VertexDataBuffer::set_size
+//       Access: Public
+//  Description: Changes the size of the buffer.  The new size must be
+//               less than or equal to the "reserved" size, which can
+//               only be changed via clean_realloc() or
+//               unclean_realloc().
+////////////////////////////////////////////////////////////////////
+INLINE void VertexDataBuffer::
+set_size(size_t size) {
+  LightMutexHolder holder(_lock);
+  nassertv(size <= _reserved_size);
+
+  if (size != _size) {
+    if (_resident_data == (unsigned char *)NULL && _reserved_size != 0) {
+      do_page_in();
+    }
+
+    _size = size;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VertexDataBuffer::clean_realloc
 //       Access: Public
-//  Description: Changes the size of the buffer, preserving its data
-//               (except for any data beyond the new end of the
-//               buffer, if the buffer is being reduced).  If the
+//  Description: Changes the "reserved" size of the buffer, preserving
+//               its data (except for any data beyond the new end of
+//               the buffer, if the buffer is being reduced).  If the
 //               buffer is expanded, the new data is uninitialized.
+//
+//               It is an error to set the reserved size smaller than
+//               the size specified with set_size().
 ////////////////////////////////////////////////////////////////////
 INLINE void VertexDataBuffer::
-clean_realloc(size_t size) {
+clean_realloc(size_t reserved_size) {
   LightMutexHolder holder(_lock);
-  do_clean_realloc(size);
+  do_clean_realloc(reserved_size);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -133,11 +180,14 @@ clean_realloc(size_t size) {
 //  Description: Changes the size of the buffer, without regard to
 //               preserving its data.  The buffer may contain random
 //               data after this call.
+//
+//               It is an error to set the reserved size smaller than
+//               the size specified with set_size().
 ////////////////////////////////////////////////////////////////////
 INLINE void VertexDataBuffer::
-unclean_realloc(size_t size) {
+unclean_realloc(size_t reserved_size) {
   LightMutexHolder holder(_lock);
-  do_unclean_realloc(size);
+  do_unclean_realloc(reserved_size);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -147,7 +197,9 @@ unclean_realloc(size_t size) {
 ////////////////////////////////////////////////////////////////////
 INLINE void VertexDataBuffer::
 clear() {
-  unclean_realloc(0);
+  LightMutexHolder holder(_lock);
+  _size = 0;
+  do_unclean_realloc(0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -165,27 +217,3 @@ page_out(VertexDataBook &book) {
   LightMutexHolder holder(_lock);
   do_page_out(book);
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: VertexDataBuffer::swap
-//       Access: Public
-//  Description: Swaps the data buffers between this one and the other
-//               one.
-////////////////////////////////////////////////////////////////////
-INLINE void VertexDataBuffer::
-swap(VertexDataBuffer &other) {
-  LightMutexHolder holder(_lock);
-  LightMutexHolder holder2(other._lock);
-
-  unsigned char *resident_data = _resident_data;
-  size_t size = _size;
-  PT(VertexDataBlock) block = _block;
-
-  _resident_data = other._resident_data;
-  _size = other._size;
-  _block = other._block;
-
-  other._resident_data = resident_data;
-  other._size = size;
-  other._block = block;
-}

+ 100 - 39
panda/src/gobj/vertexDataBuffer.cxx

@@ -28,89 +28,134 @@ operator = (const VertexDataBuffer &copy) {
   LightMutexHolder holder2(copy._lock);
 
   if (_resident_data != (unsigned char *)NULL) {
-    nassertv(_size != 0);
-    get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_size);
+    nassertv(_reserved_size != 0);
+    get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_reserved_size);
     PANDA_FREE_ARRAY(_resident_data);
     _resident_data = NULL;
   }
-  if (copy._resident_data != (unsigned char *)NULL) {
-    nassertv(copy._size != 0);
+  if (copy._resident_data != (unsigned char *)NULL && copy._size != 0) {
+    // We only allocate _size bytes, not the full _reserved_size
+    // allocated by the original copy.
     get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)copy._size);
     _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(copy._size);
     memcpy(_resident_data, copy._resident_data, copy._size);
   }
   _size = copy._size;
+  _reserved_size = copy._size;
   _block = copy._block;
+  nassertv(_reserved_size >= _size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VertexDataBuffer::swap
+//       Access: Public
+//  Description: Swaps the data buffers between this one and the other
+//               one.
+////////////////////////////////////////////////////////////////////
+void VertexDataBuffer::
+swap(VertexDataBuffer &other) {
+  LightMutexHolder holder(_lock);
+  LightMutexHolder holder2(other._lock);
+
+  unsigned char *resident_data = _resident_data;
+  size_t size = _size;
+  size_t reserved_size = _reserved_size;
+  PT(VertexDataBlock) block = _block;
+
+  _resident_data = other._resident_data;
+  _size = other._size;
+  _reserved_size = other._reserved_size;
+  _block = other._block;
+
+  other._resident_data = resident_data;
+  other._size = size;
+  other._reserved_size = reserved_size;
+  other._block = block;
+  nassertv(_reserved_size >= _size);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: VertexDataBuffer::do_clean_realloc
 //       Access: Private
-//  Description: Changes the size of the buffer, preserving its data
-//               (except for any data beyond the new end of the
-//               buffer, if the buffer is being reduced).  If the
+//  Description: Changes the reserved size of the buffer, preserving
+//               its data (except for any data beyond the new end of
+//               the buffer, if the buffer is being reduced).  If the
 //               buffer is expanded, the new data is uninitialized.
 //
 //               Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 void VertexDataBuffer::
-do_clean_realloc(size_t size) {
-  if (size != _size) {
-    if (size == 0) {
-      do_unclean_realloc(size);
+do_clean_realloc(size_t reserved_size) {
+  if (reserved_size != _reserved_size) {
+    if (reserved_size == 0 || _size == 0) {
+      do_unclean_realloc(reserved_size);
       return;
     }      
 
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << this << ".clean_realloc(" << reserved_size << ")\n";
+    }
+
     // Page in if we're currently paged out.
-    if (_size != 0 && _resident_data == (unsigned char *)NULL) {
+    if (_reserved_size != 0 && _resident_data == (unsigned char *)NULL) {
       do_page_in();
     }
     
-    get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)size - (int)_size);
-    if (_size == 0) {
+    get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)reserved_size - (int)_reserved_size);
+    if (_reserved_size == 0) {
       nassertv(_resident_data == (unsigned char *)NULL);
-      _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(size);
+      _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(reserved_size);
     } else {
       nassertv(_resident_data != (unsigned char *)NULL);
-      _resident_data = (unsigned char *)PANDA_REALLOC_ARRAY(_resident_data, size);
+      _resident_data = (unsigned char *)PANDA_REALLOC_ARRAY(_resident_data, reserved_size);
     }
     nassertv(_resident_data != (unsigned char *)NULL);
-    _size = size;
+    _reserved_size = reserved_size;
   }
+
+  _size = min(_size, _reserved_size);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: VertexDataBuffer::do_unclean_realloc
 //       Access: Private
-//  Description: Changes the size of the buffer, without regard to
-//               preserving its data.  The buffer may contain random
-//               data after this call.
+//  Description: Changes the reserved size of the buffer, without
+//               regard to preserving its data.  This implicitly
+//               resets the size to 0.
 //
 //               Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 void VertexDataBuffer::
-do_unclean_realloc(size_t size) {
-  if (size != _size || _resident_data == (unsigned char *)NULL) {
+do_unclean_realloc(size_t reserved_size) {
+  if (reserved_size != _reserved_size || _resident_data == (unsigned char *)NULL) {
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << this << ".unclean_realloc(" << reserved_size << ")\n";
+    }
+
     // If we're paged out, discard the page.
     _block = NULL;
         
     if (_resident_data != (unsigned char *)NULL) {
-      nassertv(_size != 0);
+      nassertv(_reserved_size != 0);
 
-      get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_size);
+      get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_reserved_size);
       PANDA_FREE_ARRAY(_resident_data);
       _resident_data = NULL;
-      _size = 0;
+      _reserved_size = 0;
     }
 
-    if (size != 0) {
-      get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)size);
+    if (reserved_size != 0) {
+      get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)reserved_size);
       nassertv(_resident_data == (unsigned char *)NULL);
-      _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(size);
+      _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(reserved_size);
     }
 
-    _size = size;
+    _reserved_size = reserved_size;
   }
+
+  _size = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -127,21 +172,36 @@ do_unclean_realloc(size_t size) {
 ////////////////////////////////////////////////////////////////////
 void VertexDataBuffer::
 do_page_out(VertexDataBook &book) {
-  if (_block != (VertexDataBlock *)NULL || _size == 0) {
+  if (_block != (VertexDataBlock *)NULL || _reserved_size == 0) {
     // We're already paged out.
     return;
   }
   nassertv(_resident_data != (unsigned char *)NULL);
 
-  _block = book.alloc(_size);
-  nassertv(_block != (VertexDataBlock *)NULL);
-  unsigned char *pointer = _block->get_pointer(true);
-  nassertv(pointer != (unsigned char *)NULL);
-  memcpy(pointer, _resident_data, _size);
+  if (_size == 0) {
+    // It's an empty buffer.  Just deallocate it; don't bother to
+    // create a block.
+    get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_reserved_size);
+    PANDA_FREE_ARRAY(_resident_data);
+    _resident_data = NULL;
+    _reserved_size = 0;
+
+  } else {
+    // It's a nonempty buffer, so write _size bytes (but not the full
+    // _reserved_size bytes) to a block.
+    _block = book.alloc(_size);
+    nassertv(_block != (VertexDataBlock *)NULL);
+    unsigned char *pointer = _block->get_pointer(true);
+    nassertv(pointer != (unsigned char *)NULL);
+    memcpy(pointer, _resident_data, _size);
+    
+    get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_reserved_size);
+    PANDA_FREE_ARRAY(_resident_data);
+    _resident_data = NULL;
 
-  get_class_type().dec_memory_usage(TypeHandle::MC_array, (int)_size);
-  PANDA_FREE_ARRAY(_resident_data);
-  _resident_data = NULL;
+    _reserved_size = _size;
+  }
+  nassertv(_reserved_size >= _size);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -155,12 +215,13 @@ do_page_out(VertexDataBook &book) {
 ////////////////////////////////////////////////////////////////////
 void VertexDataBuffer::
 do_page_in() {
-  if (_resident_data != (unsigned char *)NULL || _size == 0) {
+  if (_resident_data != (unsigned char *)NULL || _reserved_size == 0) {
     // We're already paged in.
     return;
   }
 
   nassertv(_block != (VertexDataBlock *)NULL);
+  nassertv(_reserved_size == _size);
 
   get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)_size);
   _resident_data = (unsigned char *)PANDA_MALLOC_ARRAY(_size);

+ 10 - 5
panda/src/gobj/vertexDataBuffer.h

@@ -33,14 +33,16 @@
 //
 //               independent - the buffer's memory is resident, and
 //               owned by the VertexDataBuffer object itself (in
-//               _resident_data).
+//               _resident_data).  In this state, _reserved_size might
+//               be greater than or equal to _size.
 //
 //               paged - the buffer's memory is owned by a
 //               VertexDataBlock.  That block might itself be
 //               resident, compressed, or paged to disk.  If it is
 //               resident, the memory may still be accessed directly
 //               from the block.  However, this memory is considered
-//               read-only.
+//               read-only.  In this state, _reserved_size will always
+//               equal _size.
 //
 //               VertexDataBuffers start out in independent state.
 //               They get moved to paged state when their owning
@@ -68,13 +70,15 @@ public:
   INLINE unsigned char *get_write_pointer();
 
   INLINE size_t get_size() const;
-  INLINE void clean_realloc(size_t size);
-  INLINE void unclean_realloc(size_t size);
+  INLINE size_t get_reserved_size() const;
+  INLINE void set_size(size_t size);
+  INLINE void clean_realloc(size_t reserved_size);
+  INLINE void unclean_realloc(size_t reserved_size);
   INLINE void clear();
 
   INLINE void page_out(VertexDataBook &book);
 
-  INLINE void swap(VertexDataBuffer &other);
+  void swap(VertexDataBuffer &other);
 
 private:
   void do_clean_realloc(size_t size);
@@ -85,6 +89,7 @@ private:
 
   unsigned char *_resident_data;
   size_t _size;
+  size_t _reserved_size;
   PT(VertexDataBlock) _block;
   LightMutex _lock;
 

+ 63 - 31
panda/src/parametrics/ropeNode.cxx

@@ -26,7 +26,7 @@
 #include "datagramIterator.h"
 #include "pStatTimer.h"
 #include "geom.h"
-#include "geomLinestrips.h"
+#include "geomLines.h"
 #include "geomTristrips.h"
 #include "geomVertexWriter.h"
 #include "boundingSphere.h"
@@ -352,27 +352,27 @@ void RopeNode::
 render_thread(CullTraverser *trav, CullTraverserData &data, 
               NurbsCurveResult *result) const {
   CurveSegments curve_segments;
-  get_connected_segments(curve_segments, result);
+  int num_curve_verts = get_connected_segments(curve_segments, result);
 
   // Now we have stored one or more sequences of vertices down the
-  // center strips.  Go back through and calculate the vertices on
-  // either side.
+  // thread.  These map directly to primitive vertices.
   PT(GeomVertexData) vdata = new GeomVertexData
     ("rope", get_format(false), Geom::UH_stream);
-  
-  compute_thread_vertices(vdata, curve_segments);
-  
-  PT(GeomLinestrips) strip = new GeomLinestrips(Geom::UH_stream);
-  CurveSegments::const_iterator si;
-  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
-    const CurveSegment &segment = (*si);
-    
-    strip->add_next_vertices(segment.size());
-    strip->close_primitive();
+  compute_thread_vertices(vdata, curve_segments, num_curve_verts);
+
+  // We use GeomLines instead of GeomLinestrips, since that can more
+  // easily be rendered directly.
+  PT(GeomLines) lines = new GeomLines(Geom::UH_stream);
+  lines->reserve_num_vertices((num_curve_verts - 1) * 2);
+
+  for (int vi = 0; vi < num_curve_verts - 1; ++vi) {
+    lines->add_vertex(vi);
+    lines->add_vertex(vi + 1);
+    lines->close_primitive();
   }
   
   PT(Geom) geom = new Geom(vdata);
-  geom->add_primitive(strip);
+  geom->add_primitive(lines);
   
   CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, get_thickness());
   CPT(RenderState) state = data._state->add_attrib(thick);
@@ -402,7 +402,7 @@ void RopeNode::
 render_tape(CullTraverser *trav, CullTraverserData &data, 
             NurbsCurveResult *result) const {
   CurveSegments curve_segments;
-  get_connected_segments(curve_segments, result);
+  int num_curve_verts = get_connected_segments(curve_segments, result);
 
   // Now we have stored one or more sequences of vertices down the
   // center strips.  Go back through and calculate the vertices on
@@ -411,8 +411,10 @@ render_tape(CullTraverser *trav, CullTraverserData &data,
     ("rope", get_format(false), Geom::UH_stream);
   
   compute_billboard_vertices(vdata, -get_tube_up(), 
-                             curve_segments, result);
-  
+                             curve_segments, num_curve_verts, result);
+
+  // Since this will be a nonindexed primitive, no need to pre-reserve
+  // the number of vertices.
   PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
   CurveSegments::const_iterator si;
   for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
@@ -459,7 +461,7 @@ render_billboard(CullTraverser *trav, CullTraverserData &data,
   LVector3f camera_vec = LVector3f::forward() * rel_transform->get_mat();
 
   CurveSegments curve_segments;
-  get_connected_segments(curve_segments, result);
+  int num_curve_verts = get_connected_segments(curve_segments, result);
 
   // Now we have stored one or more sequences of vertices down the
   // center strips.  Go back through and calculate the vertices on
@@ -468,8 +470,10 @@ render_billboard(CullTraverser *trav, CullTraverserData &data,
     ("rope", get_format(false), Geom::UH_stream);
   
   compute_billboard_vertices(vdata, camera_vec, 
-                             curve_segments, result);
+                             curve_segments, num_curve_verts, result);
   
+  // Since this will be a nonindexed primitive, no need to pre-reserve
+  // the number of vertices.
   PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
   CurveSegments::const_iterator si;
   for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
@@ -508,7 +512,7 @@ void RopeNode::
 render_tube(CullTraverser *trav, CullTraverserData &data, 
             NurbsCurveResult *result) const {
   CurveSegments curve_segments;
-  get_connected_segments(curve_segments, result);
+  int num_curve_verts = get_connected_segments(curve_segments, result);
 
   // Now, we build up a table of vertices, in a series of rings
   // around the circumference of the tube.
@@ -520,11 +524,13 @@ render_tube(CullTraverser *trav, CullTraverserData &data,
     ("rope", get_format(true), Geom::UH_stream);
   
   compute_tube_vertices(vdata, num_verts_per_slice, 
-                        curve_segments, result);
+                        curve_segments, num_curve_verts, result);
   
+  // Finally, go through and build up the index array, to tie all the
+  // triangle strips together.  This is difficult to pre-calculate the
+  // number of vertices we'll use, so we'll just let it dynamically
+  // allocate.
   PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
-  // Finally, go through build up the index array, to tie all the
-  // triangle strips together.
   int vi = 0;
   CurveSegments::const_iterator si;
   for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
@@ -571,10 +577,15 @@ render_tube(CullTraverser *trav, CullTraverserData &data,
 //               segments from the NurbsCurveEvaluator into a single
 //               CurveSegment, if they happen to be connected (as most
 //               will be).
+//
+//               The return value is the total number of points across
+//               all segments.
 ////////////////////////////////////////////////////////////////////
-void RopeNode::
+int RopeNode::
 get_connected_segments(RopeNode::CurveSegments &curve_segments,
                        const NurbsCurveResult *result) const {
+  int num_curve_verts = 0;
+
   int num_verts = get_num_subdiv() + 1;
   int num_segments = result->get_num_segments();
   bool use_vertex_color = get_use_vertex_color();
@@ -610,6 +621,7 @@ get_connected_segments(RopeNode::CurveSegments &curve_segments,
       }
 
       curve_segment->push_back(vtx);
+      ++num_curve_verts;
     }
 
     // Store all the remaining points in this segment.
@@ -631,10 +643,13 @@ get_connected_segments(RopeNode::CurveSegments &curve_segments,
       }
 
       curve_segment->push_back(vtx);
+      ++num_curve_verts;
 
       last_point = vtx._p;
     }
   }
+
+  return num_curve_verts;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -646,7 +661,10 @@ get_connected_segments(RopeNode::CurveSegments &curve_segments,
 ////////////////////////////////////////////////////////////////////
 void RopeNode::
 compute_thread_vertices(GeomVertexData *vdata,
-                        const RopeNode::CurveSegments &curve_segments) const {
+                        const RopeNode::CurveSegments &curve_segments,
+                        int num_curve_verts) const {
+  vdata->set_num_rows(num_curve_verts);
+
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
@@ -678,6 +696,8 @@ compute_thread_vertices(GeomVertexData *vdata,
       }
     }
   }
+
+  nassertv(vdata->get_num_rows() == num_curve_verts);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -691,7 +711,11 @@ void RopeNode::
 compute_billboard_vertices(GeomVertexData *vdata,
                            const LVector3f &camera_vec,
                            const RopeNode::CurveSegments &curve_segments,
+                           int num_curve_verts,
                            NurbsCurveResult *result) const {
+  int expected_num_verts = num_curve_verts * 2;
+  vdata->set_num_rows(expected_num_verts);
+
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
@@ -741,6 +765,8 @@ compute_billboard_vertices(GeomVertexData *vdata,
       }
     }
   }
+
+  nassertv(vdata->get_num_rows() == expected_num_verts);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -754,12 +780,8 @@ void RopeNode::
 compute_tube_vertices(GeomVertexData *vdata,
                       int &num_verts_per_slice,
                       const RopeNode::CurveSegments &curve_segments,
+                      int num_curve_verts,
                       NurbsCurveResult *result) const {
-  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
-  GeomVertexWriter normal(vdata, InternalName::get_normal());
-  GeomVertexWriter color(vdata, InternalName::get_color());
-  GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
-
   int num_slices = get_num_slices();
   num_verts_per_slice = num_slices;
 
@@ -780,6 +802,14 @@ compute_tube_vertices(GeomVertexData *vdata,
     ++num_verts_per_slice;
   }
 
+  int expected_num_verts = num_curve_verts * num_verts_per_slice;
+  vdata->set_num_rows(expected_num_verts);
+
+  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
+  GeomVertexWriter normal(vdata, InternalName::get_normal());
+  GeomVertexWriter color(vdata, InternalName::get_color());
+  GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
+
   LVector3f up = get_tube_up();
 
   float dist = 0.0f;
@@ -827,6 +857,8 @@ compute_tube_vertices(GeomVertexData *vdata,
       }
     }
   }
+
+  nassertv(vdata->get_num_rows() == expected_num_verts);
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -173,18 +173,21 @@ private:
   typedef pvector<CurveVertex> CurveSegment;
   typedef pvector<CurveSegment> CurveSegments;
 
-  void get_connected_segments(CurveSegments &curve_segments,
-                              const NurbsCurveResult *result) const;
+  int get_connected_segments(CurveSegments &curve_segments,
+                             const NurbsCurveResult *result) const;
 
   void compute_thread_vertices(GeomVertexData *vdata,
-                               const CurveSegments &curve_segments) const;
+                               const CurveSegments &curve_segments,
+                               int num_curve_verts) const;
   void compute_billboard_vertices(GeomVertexData *vdata,
                                   const LVector3f &camera_vec,
                                   const CurveSegments &curve_segments,
+                                  int num_curve_verts,
                                   NurbsCurveResult *result) const;
   void compute_tube_vertices(GeomVertexData *vdata,
                              int &num_verts_per_slice,
                              const CurveSegments &curve_segments,
+                             int num_curve_verts,
                              NurbsCurveResult *result) const;
 
   static void compute_tangent(LVector3f &tangent, const CurveSegment &segment,

+ 34 - 16
panda/src/parametrics/sheetNode.cxx

@@ -300,18 +300,18 @@ render_sheet(CullTraverser *trav, CullTraverserData &data,
   }
   PT(GeomVertexData) vdata = new GeomVertexData
     ("sheet", format, Geom::UH_stream);
+  int expected_num_vertices = num_u_segments * (num_u_verts + 1) * num_v_segments * num_v_verts;
+  vdata->reserve_num_rows(expected_num_vertices);
+
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter normal(vdata, InternalName::get_normal());
   GeomVertexWriter color(vdata, InternalName::get_color());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
-  
-  PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
+
   for (int ui = 0; ui < num_u_segments; ui++) {
-    for (int uni = 0; uni < num_u_verts; uni++) {
+    for (int uni = 0; uni <= num_u_verts; uni++) {
       float u0 = (float)uni / (float)num_u_verts;
-      float u1 = (float)(uni + 1) / (float)num_u_verts;
       float u0_tc = result->get_segment_u(ui, u0);
-      float u1_tc = result->get_segment_u(ui, u1);
       
       for (int vi = 0; vi < num_v_segments; vi++) {
         for (int vni = 0; vni < num_v_verts; vni++) {
@@ -326,26 +326,44 @@ render_sheet(CullTraverser *trav, CullTraverserData &data,
           normal.add_data3f(norm);
           texcoord.add_data2f(u0_tc, v_tc);
           
-          result->eval_segment_point(ui, vi, u1, v, point);
-          result->eval_segment_normal(ui, vi, u1, v, norm);
-          vertex.add_data3f(point);
-          normal.add_data3f(norm);
-          texcoord.add_data2f(u1_tc, v_tc);
-          
           if (use_vertex_color) {
-            Colorf c0, c1;
+            Colorf c0;
             result->eval_segment_extended_points(ui, vi, u0, v, 0, &c0[0], 4);
-            result->eval_segment_extended_points(ui, vi, u1, v, 0, &c1[0], 4);
-            
             color.add_data4f(c0);
-            color.add_data4f(c1);
           }
         }
-        strip->add_next_vertices(num_v_verts * 2);
+      }
+    }
+  }
+  nassertv(vdata->get_num_rows() == expected_num_vertices);
+  
+  PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
+
+  int expected_num_tristrips = num_u_segments * num_u_verts * num_v_segments;
+  int expected_verts_per_tristrip = num_v_verts * 2;
+
+  int expected_prim_vertices = (expected_num_tristrips - 1) * (expected_verts_per_tristrip + strip->get_num_unused_vertices_per_primitive()) + expected_verts_per_tristrip;
+
+  strip->reserve_num_vertices(expected_prim_vertices);
+
+  int verts_per_row = num_v_segments * num_v_verts;
+
+  for (int ui = 0; ui < num_u_segments; ui++) {
+    for (int uni = 0; uni < num_u_verts; uni++) {
+      int row_start_index = ((ui * (num_u_verts + 1)) + uni) * verts_per_row;
+
+      for (int vi = 0; vi < num_v_segments; vi++) {
+        for (int vni = 0; vni < num_v_verts; vni++) {
+          int vert_index_0 = row_start_index + (vi * num_v_verts) + vni;
+          int vert_index_1 = vert_index_0 + verts_per_row;
+          strip->add_vertex(vert_index_0);
+          strip->add_vertex(vert_index_1);
+        }
         strip->close_primitive();
       }
     }
   }
+  nassertv(strip->get_num_vertices() == expected_prim_vertices);
   
   PT(Geom) geom = new Geom(vdata);
   geom->add_primitive(strip);

+ 1 - 0
panda/src/text/textAssembler.cxx

@@ -1584,6 +1584,7 @@ draw_underscore(TextAssembler::PlacedGlyphs &row_placed_glyphs,
   CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp();
   PT(GeomVertexData) vdata = 
     new GeomVertexData("text", format, Geom::UH_static);
+  vdata->reserve_num_rows(2);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());