Browse Source

Support strip cut indices and (using those) support direct rendering of linestrips in OpenGL

rdb 11 years ago
parent
commit
a25a9e655e

+ 8 - 2
panda/src/display/standardMunger.cxx

@@ -178,7 +178,10 @@ munge_geom_impl(CPT(Geom) &geom, CPT(GeomVertexData) &vertex_data,
     // Even beyond munging the vertex format, we have to convert the
     // Geom itself into a new primitive type the GSG can render
     // directly.
-    if ((unsupported_bits & Geom::GR_composite_bits) != 0) {
+    // If we don't support a strip cut index, it might be faster to
+    // just decompose it rather than draw them one by one.
+    if ((unsupported_bits & Geom::GR_composite_bits) != 0 ||
+        (unsupported_bits & Geom::GR_strip_cut_index) != 0) {
       // This decomposes everything in the primitive, so that if (for
       // instance) the primitive contained both strips and fans, but
       // the GSG didn't support fans, it would decompose the strips
@@ -224,7 +227,10 @@ premunge_geom_impl(CPT(Geom) &geom, CPT(GeomVertexData) &vertex_data) {
     // Even beyond munging the vertex format, we have to convert the
     // Geom itself into a new primitive type the GSG can render
     // directly.
-    if ((unsupported_bits & Geom::GR_composite_bits) != 0) {
+    // If we don't support a strip cut index, it might be faster to
+    // just decompose it rather than draw them one by one.
+    if ((unsupported_bits & Geom::GR_composite_bits) != 0 ||
+        (unsupported_bits & Geom::GR_strip_cut_index) != 0) {
       // This decomposes everything in the primitive, so that if (for
       // instance) the primitive contained both strips and fans, but
       // the GSG didn't support fans, it would decompose the strips

+ 132 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -515,6 +515,7 @@ reset() {
     Geom::GR_point | Geom::GR_point_uniform_size |
     Geom::GR_indexed_other |
     Geom::GR_triangle_strip | Geom::GR_triangle_fan |
+    Geom::GR_line_strip |
     Geom::GR_flat_last_vertex;
 
   _supports_point_parameters = false;
@@ -551,6 +552,30 @@ reset() {
     _supported_geom_rendering |= Geom::GR_point_sprite;
   }
 
+  _glPrimitiveRestartIndex = NULL;
+
+  if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_ES3_compatibility")) {
+    // As long as we enable this, OpenGL will always use the highest possible index
+    // for a numeric type as strip cut index, which coincides with our convention.
+    // This saves us a call to glPrimitiveRestartIndex.
+    glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+    _supported_geom_rendering |= Geom::GR_strip_cut_index;
+
+  } else if (is_at_least_gl_version(3, 1)) {
+    glEnable(GL_PRIMITIVE_RESTART);
+    _supported_geom_rendering |= Geom::GR_strip_cut_index;
+
+    _glPrimitiveRestartIndex = (PFNGLPRIMITIVERESTARTINDEXPROC)
+      get_extension_func("glPrimitiveRestartIndex");
+
+  } else if (has_extension("GL_NV_primitive_restart")) {
+    glEnable(GL_PRIMITIVE_RESTART_NV);
+    _supported_geom_rendering |= Geom::GR_strip_cut_index;
+
+    _glPrimitiveRestartIndex = (PFNGLPRIMITIVERESTARTINDEXPROC)
+      get_extension_func("glPrimitiveRestartIndexNV");
+  }
+
   _supports_vertex_blend = has_extension("GL_ARB_vertex_blend");
 
   if (_supports_vertex_blend) {
@@ -3800,7 +3825,113 @@ draw_lines(const GeomPrimitivePipelineReader *reader, bool force) {
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
 draw_linestrips(const GeomPrimitivePipelineReader *reader, bool force) {
-  return false;
+  PStatGPUTimer timer(this, _draw_primitive_pcollector, reader->get_current_thread());
+
+  report_my_gl_errors();
+
+#ifndef NDEBUG
+  if (GLCAT.is_spam()) {
+    GLCAT.spam() << "draw_linestrips: " << *(reader->get_object()) << "\n";
+  }
+#endif  // NDEBUG
+
+#ifdef SUPPORT_IMMEDIATE_MODE
+  if (_use_sender) {
+    draw_immediate_composite_primitives(reader, GL_LINE_STRIP);
+
+  } else
+#endif  // SUPPORT_IMMEDIATE_MODE
+  {
+    if (reader->is_indexed() &&
+        (_supported_geom_rendering & GeomEnums::GR_strip_cut_index) != 0) {
+      // One long triangle strip, connected by strip cut indices.
+      if (_glPrimitiveRestartIndex != NULL) {
+        _glPrimitiveRestartIndex(reader->get_strip_cut_index());
+      }
+
+      int num_vertices = reader->get_num_vertices();
+      _vertices_other_pcollector.add_level(num_vertices);
+      _primitive_batches_other_pcollector.add_level(1);
+
+      const unsigned char *client_pointer;
+      if (!setup_primitive(client_pointer, reader, force)) {
+        return false;
+      }
+#ifndef OPENGLES
+      if (_supports_geometry_instancing && _instance_count > 0) {
+        _glDrawElementsInstanced(GL_LINE_STRIP, num_vertices,
+                                 get_numeric_type(reader->get_index_type()),
+                                 client_pointer, _instance_count);
+      } else
+#endif
+      {
+        _glDrawRangeElements(GL_LINE_STRIP,
+                             reader->get_min_vertex(),
+                             reader->get_max_vertex(),
+                             num_vertices,
+                             get_numeric_type(reader->get_index_type()),
+                             client_pointer);
+      }
+    } else {
+      // Send the individual line strips, stepping over the
+      // strip-cut indices.
+      CPTA_int ends = reader->get_ends();
+
+      _primitive_batches_other_pcollector.add_level(ends.size());
+      if (reader->is_indexed()) {
+        const unsigned char *client_pointer;
+        if (!setup_primitive(client_pointer, reader, force)) {
+          return false;
+        }
+        int index_stride = reader->get_index_stride();
+        GeomVertexReader mins(reader->get_mins(), 0);
+        GeomVertexReader maxs(reader->get_maxs(), 0);
+        nassertr(reader->get_mins()->get_num_rows() == (int)ends.size() &&
+                 reader->get_maxs()->get_num_rows() == (int)ends.size(), false);
+
+        unsigned int start = 0;
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_other_pcollector.add_level(ends[i] - start);
+#ifndef OPENGLES
+          if (_supports_geometry_instancing && _instance_count > 0) {
+            _glDrawElementsInstanced(GL_LINE_STRIP, ends[i] - start,
+                                     get_numeric_type(reader->get_index_type()),
+                                     client_pointer + start * index_stride,
+                                     _instance_count);
+          } else
+#endif
+          {
+            _glDrawRangeElements(GL_LINE_STRIP,
+                                 mins.get_data1i(), maxs.get_data1i(),
+                                 ends[i] - start,
+                                 get_numeric_type(reader->get_index_type()),
+                                 client_pointer + start * index_stride);
+          }
+          start = ends[i] + 1;
+        }
+      } else {
+        unsigned int start = 0;
+        int first_vertex = reader->get_first_vertex();
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_other_pcollector.add_level(ends[i] - start);
+#ifndef OPENGLES
+          if (_supports_geometry_instancing && _instance_count > 0) {
+            _glDrawArraysInstanced(GL_LINE_STRIP, first_vertex + start,
+                                   ends[i] - start, _instance_count);
+          } else
+#endif
+          {
+            glDrawArrays(GL_LINE_STRIP, first_vertex + start,
+                            ends[i] - start);
+          }
+          start = ends[i] + 1;
+        }
+      }
+    }
+  }
+
+  report_my_gl_errors();
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -598,6 +598,8 @@ public:
   PFNGLPOINTPARAMETERFVPROC _glPointParameterfv;
   bool _supports_point_sprite;
 
+  PFNGLPRIMITIVERESTARTINDEXPROC _glPrimitiveRestartIndex;
+
   bool _supports_vertex_blend;
   PFNGLWEIGHTPOINTERARBPROC _glWeightPointer;
   PFNGLVERTEXBLENDARBPROC _glVertexBlend;

+ 5 - 0
panda/src/glstuff/panda_glext.h

@@ -1163,6 +1163,11 @@ extern "C" {
 /* reuse GL_TEXTURE_IMMUTABLE_FORMAT */
 #endif
 
+#ifndef GL_VERSION_4_3
+#define GL_VERSION_4_3 1
+#define GL_PRIMITIVE_RESTART_FIXED_INDEX  0x8D69
+#endif
+
 #ifndef GL_ARB_multitexture
 #define GL_TEXTURE0_ARB                   0x84C0
 #define GL_TEXTURE1_ARB                   0x84C1

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

@@ -125,6 +125,9 @@ PUBLISHED:
     // The union of all of the above composite types.
     GR_composite_bits       = 0x01c00,
 
+    // If strip-cut indices are used to restart a composite primitive.
+    GR_strip_cut_index      = 0x20000,
+
     // If the shade model requires a particular vertex for flat shading.
     GR_flat_first_vertex    = 0x02000,
     GR_flat_last_vertex     = 0x04000,
@@ -216,4 +219,3 @@ EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::NumericType nume
 EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::Contents contents);
 
 #endif
-

+ 51 - 2
panda/src/gobj/geomLinestrips.cxx

@@ -89,7 +89,11 @@ get_primitive_type() const {
 int GeomLinestrips::
 get_geom_rendering() const {
   if (is_indexed()) {
-    return GR_line_strip | GR_indexed_other;
+    if (get_num_primitives() > 1) {
+      return GR_line_strip | GR_indexed_other | GR_strip_cut_index; 
+    } else {
+      return GR_line_strip | GR_indexed_other;
+    }
   } else {
     return GR_line_strip;
   }
@@ -106,6 +110,20 @@ get_min_num_vertices_per_primitive() const {
   return 2;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomLinestrips::get_num_unused_vertices_per_primitive
+//       Access: Public, Virtual
+//  Description: Returns the number of vertices that are added between
+//               primitives that aren't, strictly speaking, part of
+//               the primitives themselves.  This is used, for
+//               instance, to define degenerate triangles to connect
+//               otherwise disconnected triangle strips.
+////////////////////////////////////////////////////////////////////
+int GeomLinestrips::
+get_num_unused_vertices_per_primitive() const {
+  return 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomLinestrips::draw
 //       Access: Public, Virtual
@@ -138,9 +156,13 @@ decompose_impl() const {
   lines->set_shade_model(get_shade_model());
   CPTA_int ends = get_ends();
 
-  int vi = 0;
+  int num_unused = get_num_unused_vertices_per_primitive();
+
+  int vi = -num_unused;
   int li = 0;
   while (li < (int)ends.size()) {
+    // Skip unused vertices between tristrips.
+    vi += num_unused;
     int end = ends[li];
     nassertr(vi + 1 <= end, lines.p());
     int v0 = get_vertex(vi);
@@ -210,6 +232,33 @@ rotate_impl() const {
   return new_vertices;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomLinestrips::requires_unused_vertices
+//       Access: Protected, Virtual
+//  Description: Should be redefined to return true in any primitive
+//               that implements append_unused_vertices().
+////////////////////////////////////////////////////////////////////
+bool GeomLinestrips::
+requires_unused_vertices() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomLinestrips::append_unused_vertices
+//       Access: Protected, Virtual
+//  Description: Called when a new primitive is begun (other than the
+//               first primitive), this should add some degenerate
+//               vertices between primitives, if the primitive type
+//               requires that.  The second parameter is the first
+//               vertex that begins the new primitive.
+////////////////////////////////////////////////////////////////////
+void GeomLinestrips::
+append_unused_vertices(GeomVertexArrayData *vertices, int vertex) {
+  GeomVertexWriter to(vertices, 0);
+  to.set_row_unsafe(vertices->get_num_rows());
+  to.add_data1i(get_strip_cut_index());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomLinestrips::register_with_read_factory
 //       Access: Public, Static

+ 4 - 0
panda/src/gobj/geomLinestrips.h

@@ -34,6 +34,7 @@ public:
   virtual PrimitiveType get_primitive_type() const;
   virtual int get_geom_rendering() const;
   virtual int get_min_num_vertices_per_primitive() const;
+  virtual int get_num_unused_vertices_per_primitive() const;
 
 public:
   virtual bool draw(GraphicsStateGuardianBase *gsg,
@@ -43,6 +44,9 @@ public:
 protected:
   virtual CPT(GeomPrimitive) decompose_impl() const;
   virtual CPT(GeomVertexArrayData) rotate_impl() const;
+  virtual bool requires_unused_vertices() const;
+  virtual void append_unused_vertices(GeomVertexArrayData *vertices, 
+                                      int vertex);
 
 public:
   static void register_with_read_factory();

+ 25 - 1
panda/src/gobj/geomPrimitive.I

@@ -129,7 +129,7 @@ get_first_vertex() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::get_num_vertices
 //       Access: Published
-//  Description: Returns the number of vertices used by all the
+//  Description: Returns the number of indices used by all the
 //               primitives in this object.
 ////////////////////////////////////////////////////////////////////
 INLINE int GeomPrimitive::
@@ -310,6 +310,20 @@ get_index_stride() const {
   return reader.get_index_stride();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::get_strip_cut_index
+//       Access: Published
+//  Description: If relevant, returns the index value that may be
+//               used in some cases to signify the end of a
+//               primitive.  This is typically the highest value
+//               that the numeric type can store.
+////////////////////////////////////////////////////////////////////
+INLINE int GeomPrimitive::
+get_strip_cut_index() const {
+  CDReader cdata(_cycler);
+  return get_strip_cut_index(cdata->_index_type);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::get_ends
 //       Access: Published
@@ -679,6 +693,16 @@ get_read_pointer(bool force) const {
   return _vertices_reader->get_read_pointer(force);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitivePipelineReader::get_strip_cut_index
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int GeomPrimitivePipelineReader::
+get_strip_cut_index() const {
+  return GeomPrimitive::get_strip_cut_index(_cdata->_index_type);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitivePipelineReader::get_ends
 //       Access: Public

+ 170 - 64
panda/src/gobj/geomPrimitive.cxx

@@ -61,18 +61,18 @@ make_cow_copy() {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::Constructor
 //       Access: Published
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 GeomPrimitive::
 GeomPrimitive(GeomPrimitive::UsageHint usage_hint) {
   CDWriter cdata(_cycler, true);
   cdata->_usage_hint = usage_hint;
 }
- 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::Copy Constructor
 //       Access: Published
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 GeomPrimitive::
 GeomPrimitive(const GeomPrimitive &copy) :
@@ -80,7 +80,7 @@ GeomPrimitive(const GeomPrimitive &copy) :
   _cycler(copy._cycler)
 {
 }
-  
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::Copy Assignment Operator
 //       Access: Published
@@ -98,7 +98,7 @@ operator = (const GeomPrimitive &copy) {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::Destructor
 //       Access: Published, Virtual
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 GeomPrimitive::
 ~GeomPrimitive() {
@@ -197,7 +197,7 @@ add_vertex(int vertex) {
 
   int num_primitives = get_num_primitives();
   if (num_primitives > 0 &&
-      requires_unused_vertices() && 
+      requires_unused_vertices() &&
       get_num_vertices() == get_primitive_end(num_primitives - 1)) {
     // If we are beginning a new primitive, give the derived class a
     // chance to insert some degenerate vertices.
@@ -224,7 +224,7 @@ add_vertex(int vertex) {
       cdata->_got_minmax = false;
       return;
     }
-    
+
     // Otherwise, we need to suddenly become an indexed primitive.
     do_make_indexed(cdata);
   }
@@ -288,7 +288,7 @@ add_consecutive_vertices(int start, int num_vertices) {
       cdata->_got_minmax = false;
       return;
     }
-    
+
     // Otherwise, we need to suddenly become an indexed primitive.
     do_make_indexed(cdata);
   }
@@ -468,12 +468,18 @@ offset_vertices(int offset) {
       recompute_minmax(cdata);
       nassertv(cdata->_got_minmax);
     }
-    
+
     consider_elevate_index_type(cdata, cdata->_max_vertex + offset);
 
+    int strip_cut_index = get_strip_cut_index(cdata->_index_type);
+
     GeomVertexRewriter index(do_modify_vertices(cdata), 0);
     while (!index.is_at_end()) {
-      index.set_data1i(index.get_data1i() + offset);
+      int vertex = index.get_data1i();
+
+      if (vertex != strip_cut_index) {
+        index.set_data1i(vertex + offset);
+      }
     }
 
   } else {
@@ -483,7 +489,7 @@ offset_vertices(int offset) {
     cdata->_modified = Geom::get_next_modified();
     cdata->_got_minmax = false;
 
-    consider_elevate_index_type(cdata, 
+    consider_elevate_index_type(cdata,
                                 cdata->_first_vertex + cdata->_num_vertices - 1);
   }
 }
@@ -516,14 +522,19 @@ offset_vertices(int offset, int begin_row, int end_row) {
 
   if (is_indexed()) {
     CDWriter cdata(_cycler, true);
-    
+
+    int strip_cut_index = get_strip_cut_index(cdata->_index_type);
+
     // Calculate the maximum vertex over our range.
     int max_vertex = 0;
     {
       GeomVertexReader index_r(cdata->_vertices.get_read_pointer(), 0);
       index_r.set_row_unsafe(begin_row);
       for (int j = begin_row; j < end_row; ++j) {
-        max_vertex = max(max_vertex, index_r.get_data1i());
+        int vertex = index_r.get_data1i();
+        if (vertex != strip_cut_index) {
+          max_vertex = max(max_vertex, vertex);
+        }
       }
     }
 
@@ -532,7 +543,10 @@ offset_vertices(int offset, int begin_row, int end_row) {
     GeomVertexRewriter index(do_modify_vertices(cdata), 0);
     index.set_row_unsafe(begin_row);
     for (int j = begin_row; j < end_row; ++j) {
-      index.set_data1i(index.get_data1i() + offset);
+      int vertex = index.get_data1i();
+      if (vertex != strip_cut_index) {
+        index.set_data1i(vertex + offset);
+      }
     }
 
   } else {
@@ -544,7 +558,7 @@ offset_vertices(int offset, int begin_row, int end_row) {
     cdata->_modified = Geom::get_next_modified();
     cdata->_got_minmax = false;
 
-    consider_elevate_index_type(cdata, 
+    consider_elevate_index_type(cdata,
                                 cdata->_first_vertex + cdata->_num_vertices - 1);
   }
 }
@@ -554,17 +568,20 @@ offset_vertices(int offset, int begin_row, int end_row) {
 //       Access: Published
 //  Description: Converts the primitive from indexed to nonindexed by
 //               duplicating vertices as necessary into the indicated
-//               dest GeomVertexData.
+//               dest GeomVertexData.  Note: does not support
+//               primitives with strip cut indices.
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 make_nonindexed(GeomVertexData *dest, const GeomVertexData *source) {
   Thread *current_thread = Thread::get_current_thread();
   int num_vertices = get_num_vertices();
   int dest_start = dest->get_num_rows();
+  int strip_cut_index = get_strip_cut_index();
 
   dest->set_num_rows(dest_start + num_vertices);
   for (int i = 0; i < num_vertices; ++i) {
     int v = get_vertex(i);
+    nassertd(v != strip_cut_index) continue;
     dest->copy_row_from(dest_start + i, source, v, current_thread);
   }
 
@@ -596,14 +613,18 @@ pack_vertices(GeomVertexData *dest, const GeomVertexData *source) {
 
     int num_vertices = get_num_vertices();
     int dest_start = dest->get_num_rows();
+    int strip_cut_index = get_strip_cut_index();
 
     for (int i = 0; i < num_vertices; ++i) {
       int v = get_vertex(i);
+      if (v == strip_cut_index) {
+        continue;
+      }
 
       // Try to add the relation { v : size() }.  If that succeeds,
       // great; if it doesn't, look up whatever we previously added
       // for v.
-      pair<CopiedIndices::iterator, bool> result = 
+      pair<CopiedIndices::iterator, bool> result =
         copied_indices.insert(CopiedIndices::value_type(v, (int)copied_indices.size()));
       int v2 = (*result.first).second + dest_start;
       index.add_data1i(v2);
@@ -613,7 +634,7 @@ pack_vertices(GeomVertexData *dest, const GeomVertexData *source) {
         dest->copy_row_from(v2, source, v, current_thread);
       }
     }
-    
+
     set_vertices(new_vertices);
   }
 }
@@ -643,7 +664,7 @@ make_indexed() {
 //     Function: GeomPrimitive::get_primitive_start
 //       Access: Published
 //  Description: Returns the element within the _vertices list at which
-//               the nth primitive starts.  
+//               the nth primitive starts.
 //
 //               If i is one more than the highest valid primitive
 //               vertex, the return value will be one more than the
@@ -723,7 +744,7 @@ get_primitive_num_vertices(int n) const {
     } else {
       int num_unused_vertices_per_primitive = get_num_unused_vertices_per_primitive();
       return cdata->_ends[n] - cdata->_ends[n - 1] - num_unused_vertices_per_primitive;
-    }      
+    }
 
   } else {
     // This is a simple primitive type like a triangle: each primitive
@@ -732,6 +753,28 @@ get_primitive_num_vertices(int n) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::get_num_used_vertices
+//       Access: Published
+//  Description: Returns the number of vertices used by all of the
+//               primitives.  This is the same as summing
+//               get_primitive_num_vertices(n) for n in
+//               get_num_primitives().  It is like get_num_vertices
+//               except that it excludes all of the degenerate
+//               vertices and strip-cut indices.
+////////////////////////////////////////////////////////////////////
+int GeomPrimitive::
+get_num_used_vertices() const {
+  int num_primitives = get_num_primitives();
+
+  if (num_primitives > 0) {
+    return get_num_vertices() - ((num_primitives - 1) *
+           get_num_unused_vertices_per_primitive());
+  } else {
+    return 0;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::get_primitive_min_vertex
 //       Access: Published
@@ -959,10 +1002,14 @@ make_points() const {
   int num_vertices = get_num_vertices();
   if (is_indexed()) {
     CPT(GeomVertexArrayData) vertices = get_vertices();
+    int strip_cut_index = get_strip_cut_index();
     GeomVertexReader index(vertices, 0);
     for (int vi = 0; vi < num_vertices; ++vi) {
       nassertr(!index.is_at_end(), NULL);
-      bits.set_bit(index.get_data1i());
+      int vertex = index.get_data1i();
+      if (vertex != strip_cut_index) {
+        bits.set_bit(vertex);
+      }
     }
   } else {
     int first_vertex = get_first_vertex();
@@ -996,14 +1043,13 @@ make_points() const {
 //       Access: Published
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
-//               triangles, puts these in a new GeomPatches objectand returns a pointer to the new primitive
+//               triangles, puts these in a new GeomPatches object
+//               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.
+//               to use tesselation shaders on arbitrary geometry.
 ////////////////////////////////////////////////////////////////////
 CPT(GeomPrimitive) GeomPrimitive::
 make_patches() const {
@@ -1079,7 +1125,7 @@ request_resident() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::output
 //       Access: Published, Virtual
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 output(ostream &out) const {
@@ -1090,7 +1136,7 @@ output(ostream &out) const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::write
 //       Access: Published, Virtual
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 write(ostream &out, int indent_level) const {
@@ -1448,7 +1494,7 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 //               rendered.
 ////////////////////////////////////////////////////////////////////
 IndexBufferContext *GeomPrimitive::
-prepare_now(PreparedGraphicsObjects *prepared_objects, 
+prepare_now(PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
   nassertr(is_indexed(), NULL);
 
@@ -1558,28 +1604,57 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::get_highest_index_value
 //       Access: Private, Static
-//  Description: Returns the largest index value that can be stored in
-//               an index of the indicated type.
+//  Description: Returns the largest index value that can be stored
+//               in an index of the indicated type, minus one (to
+//               leave room for a potential strip cut index)
 ////////////////////////////////////////////////////////////////////
 int GeomPrimitive::
 get_highest_index_value(NumericType index_type) {
+  // Reserve the highest possible index because implementations use
+  // this as a strip-cut index.
   switch (index_type) {
   case NT_uint8:
-    return 0xff;
+    return 0xff - 1;
 
   case NT_uint16:
-    return 0xffff;
+    return 0xffff - 1;
 
   case NT_uint32:
     // We don't actually allow use of the sign bit, since all of our
     // functions receive an "int" instead of an "unsigned int".
-    return 0x7fffffff;
+    return 0x7fffffff - 1;
 
   default:
     return 0;
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::get_strip_cut_index
+//       Access: Private, Static
+//  Description: Returns the index of the indicated type that is
+//               reserved for use as a strip cut index, if enabled
+//               for the primitive.  When the renderer encounters
+//               this index, it will restart the primitive.  This
+//               is guaranteed not to point to an actual vertex.
+////////////////////////////////////////////////////////////////////
+int GeomPrimitive::
+get_strip_cut_index(NumericType index_type) {
+  // Reserve the highest possible index because implementations use
+  // this as a strip-cut index.
+  switch (index_type) {
+  case NT_uint8:
+    return 0xff;
+
+  case NT_uint16:
+    return 0xffff;
+
+  case NT_uint32:
+  default:
+    return -1;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::calc_tight_bounds
 //       Access: Public, Virtual
@@ -1593,7 +1668,7 @@ get_highest_index_value(NumericType index_type) {
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
-                  bool &found_any, 
+                  bool &found_any,
                   const GeomVertexData *vertex_data,
                   bool got_mat, const LMatrix4 &mat,
                   const InternalName *column_name,
@@ -1613,7 +1688,7 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
       for (int i = 0; i < cdata->_num_vertices; i++) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
         LPoint3 vertex = mat.xform_point(reader.get_data3());
-        
+
         if (found_any) {
           min_point.set(min(min_point[0], vertex[0]),
                         min(min_point[1], vertex[1]),
@@ -1631,7 +1706,7 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
       for (int i = 0; i < cdata->_num_vertices; i++) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
         const LVecBase3 &vertex = reader.get_data3();
-        
+
         if (found_any) {
           min_point.set(min(min_point[0], vertex[0]),
                         min(min_point[1], vertex[1]),
@@ -1650,13 +1725,17 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
   } else {
     // Indexed case.
     GeomVertexReader index(cdata->_vertices.get_read_pointer(), 0, current_thread);
+    int strip_cut_index = get_strip_cut_index(cdata->_index_type);
 
     if (got_mat) {
       while (!index.is_at_end()) {
         int ii = index.get_data1i();
+        if (ii == strip_cut_index) {
+          continue;
+        }
         reader.set_row_unsafe(ii);
         LPoint3 vertex = mat.xform_point(reader.get_data3());
-        
+
         if (found_any) {
           min_point.set(min(min_point[0], vertex[0]),
                         min(min_point[1], vertex[1]),
@@ -1673,9 +1752,12 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
     } else {
       while (!index.is_at_end()) {
         int ii = index.get_data1i();
+        if (ii == strip_cut_index) {
+          continue;
+        }
         reader.set_row_unsafe(ii);
         const LVecBase3 &vertex = reader.get_data3();
-        
+
         if (found_any) {
           min_point.set(min(min_point[0], vertex[0]),
                         min(min_point[1], vertex[1]),
@@ -1797,63 +1879,77 @@ recompute_minmax(GeomPrimitive::CData *cdata) {
       cdata->_max_vertex = 0;
       cdata->_mins.clear();
       cdata->_maxs.clear();
-      
+
     } else if (get_num_vertices_per_primitive() == 0) {
       // This is a complex primitive type like a triangle strip; compute
       // the minmax of each primitive (as well as the overall minmax).
       GeomVertexReader index(cdata->_vertices.get_read_pointer(), 0);
-      
+
       cdata->_mins = make_index_data();
       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;
-      
+
       unsigned int vertex = index.get_data1i();
       cdata->_min_vertex = vertex;
       cdata->_max_vertex = vertex;
       unsigned int min_prim = vertex;
       unsigned int max_prim = vertex;
-      
+
+      int num_unused_vertices = get_num_unused_vertices_per_primitive();
+
       for (int vi = 1; vi < num_vertices; ++vi) {
         nassertv(!index.is_at_end());
-        unsigned int vertex = index.get_data1i();
-        cdata->_min_vertex = min(cdata->_min_vertex, vertex);
-        cdata->_max_vertex = max(cdata->_max_vertex, vertex);
-
         nassertv(pi < (int)cdata->_ends.size());
+
+        unsigned int vertex;
+
         if (vi == cdata->_ends[pi]) {
+          // Skip unused vertices, since they won't be very relevant and
+          // may contain a strip-cut index, which would distort the result.
+          if (num_unused_vertices > 0) {
+            vi += num_unused_vertices;
+            index.set_row_unsafe(vi);
+          }
+          vertex = index.get_data1i();
+
           mins.add_data1i(min_prim);
           maxs.add_data1i(max_prim);
           min_prim = vertex;
           max_prim = vertex;
           ++pi;
-          
+
         } else {
+          vertex = index.get_data1i();
           min_prim = min(min_prim, vertex);
           max_prim = max(max_prim, vertex);
         }
+
+        cdata->_min_vertex = min(cdata->_min_vertex, vertex);
+        cdata->_max_vertex = max(cdata->_max_vertex, vertex);
       }
+
       mins.add_data1i(min_prim);
       maxs.add_data1i(max_prim);
       nassertv(mins.get_array_data()->get_num_rows() == (int)cdata->_ends.size());
-      
+
     } else {
       // This is a simple primitive type like a triangle; just compute
       // the overall minmax.
       GeomVertexReader index(cdata->_vertices.get_read_pointer(), 0);
-      
+
       cdata->_mins.clear();
       cdata->_maxs.clear();
-      
+
       unsigned int vertex = index.get_data1i();
       cdata->_min_vertex = vertex;
       cdata->_max_vertex = vertex;
-      
+
       for (int vi = 1; vi < num_vertices; ++vi) {
         nassertv(!index.is_at_end());
         unsigned int vertex = index.get_data1i();
@@ -1862,7 +1958,7 @@ recompute_minmax(GeomPrimitive::CData *cdata) {
       }
     }
   }
-    
+
   cdata->_got_minmax = true;
 }
 
@@ -1899,22 +1995,25 @@ do_make_indexed(CData *cdata) {
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 consider_elevate_index_type(CData *cdata, int vertex) {
+  // Note that we reserve the highest possible index of a particular
+  // index type (ie. -1) because this is commonly used as a strip-cut
+  // (also known as primitive restart) index.
   switch (cdata->_index_type) {
   case NT_uint8:
-    if (vertex > 0xff) {
+    if (vertex >= 0xff) {
       do_set_index_type(cdata, NT_uint16);
     }
     break;
 
   case NT_uint16:
-    if (vertex > 0xffff) {
+    if (vertex >= 0xffff) {
       do_set_index_type(cdata, NT_uint32);
     }
     break;
 
   case NT_uint32:
     // Not much we can do here.
-    nassertv(vertex <= 0x7fffffff);
+    nassertv(vertex < 0x7fffffff);
     break;
 
   default:
@@ -1929,6 +2028,9 @@ consider_elevate_index_type(CData *cdata, int vertex) {
 ////////////////////////////////////////////////////////////////////
 void GeomPrimitive::
 do_set_index_type(CData *cdata, GeomPrimitive::NumericType index_type) {
+  int old_strip_cut_index = get_strip_cut_index(cdata->_index_type);
+  int new_strip_cut_index = get_strip_cut_index(index_type);
+
   cdata->_index_type = index_type;
 
   if (gobj_cat.is_debug()) {
@@ -1938,7 +2040,7 @@ do_set_index_type(CData *cdata, GeomPrimitive::NumericType index_type) {
 
   if (!cdata->_vertices.is_null()) {
     CPT(GeomVertexArrayFormat) new_format = get_index_format();
-    
+
     CPT(GeomVertexArrayData) array_obj = cdata->_vertices.get_read_pointer();
     if (array_obj->get_array_format() != new_format) {
       PT(GeomVertexArrayData) new_vertices = make_index_data();
@@ -1946,9 +2048,13 @@ do_set_index_type(CData *cdata, GeomPrimitive::NumericType index_type) {
 
       GeomVertexReader from(array_obj, 0);
       GeomVertexWriter to(new_vertices, 0);
-      
+
       while (!from.is_at_end()) {
-        to.set_data1i(from.get_data1i());
+        int index = from.get_data1i();
+        if (index == old_strip_cut_index) {
+          index = new_strip_cut_index;
+        }
+        to.set_data1i(index);
       }
       cdata->_vertices = new_vertices;
       cdata->_got_minmax = false;
@@ -2056,7 +2162,7 @@ int GeomPrimitive::CData::
 complete_pointers(TypedWritable **p_list, BamReader *manager) {
   int pi = CycleData::complete_pointers(p_list, manager);
 
-  _vertices = DCAST(GeomVertexArrayData, p_list[pi++]);    
+  _vertices = DCAST(GeomVertexArrayData, p_list[pi++]);
 
   if (manager->get_file_minor_ver() < 6 && !_vertices.is_null()) {
     // Older bam files might have a meaningless number in
@@ -2106,7 +2212,7 @@ check_minmax() const {
 #ifdef DO_PIPELINING
       unref_delete((CycleData *)_cdata);
 #endif
-      GeomPrimitive::CDWriter fresh_cdata(((GeomPrimitive *)_object.p())->_cycler, 
+      GeomPrimitive::CDWriter fresh_cdata(((GeomPrimitive *)_object.p())->_cycler,
                                           false, _current_thread);
       ((GeomPrimitivePipelineReader *)this)->_cdata = fresh_cdata;
 #ifdef DO_PIPELINING
@@ -2132,7 +2238,7 @@ check_minmax() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitivePipelineReader::get_first_vertex
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 int GeomPrimitivePipelineReader::
 get_first_vertex() const {
@@ -2170,7 +2276,7 @@ get_vertex(int i) const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitivePipelineReader::get_num_primitives
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 int GeomPrimitivePipelineReader::
 get_num_primitives() const {

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

@@ -118,6 +118,7 @@ PUBLISHED:
   int get_primitive_start(int n) const;
   int get_primitive_end(int n) const;
   int get_primitive_num_vertices(int n) const;
+  int get_num_used_vertices() const;
 
   INLINE int get_num_faces() const;
   INLINE int get_primitive_num_faces(int n) const;
@@ -163,6 +164,7 @@ PUBLISHED:
   void set_nonindexed_vertices(int first_vertex, int num_vertices);
 
   INLINE int get_index_stride() const;
+  INLINE int get_strip_cut_index() const;
 
   INLINE CPTA_int get_ends() const;
   PTA_int modify_ends();
@@ -194,6 +196,7 @@ public:
 private:
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
   static int get_highest_index_value(NumericType index_type);
+  static int get_strip_cut_index(NumericType index_type);
 
 public:
   virtual bool draw(GraphicsStateGuardianBase *gsg,
@@ -358,6 +361,7 @@ public:
   INLINE int get_index_stride() const;
   INLINE const GeomVertexArrayDataHandle *get_vertices_reader() const;
   INLINE const unsigned char *get_read_pointer(bool force) const;
+  INLINE int get_strip_cut_index() const;
   INLINE CPTA_int get_ends() const;
   INLINE CPT(GeomVertexArrayData) get_mins() const;
   INLINE CPT(GeomVertexArrayData) get_maxs() const;

+ 16 - 4
panda/src/gobj/geomTristrips.cxx

@@ -95,6 +95,17 @@ get_geom_rendering() const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTristrips::get_min_num_vertices_per_primitive
+//       Access: Public, Virtual
+//  Description: Returns the minimum number of vertices that must be
+//               added before close_primitive() may legally be called.
+////////////////////////////////////////////////////////////////////
+int GeomTristrips::
+get_min_num_vertices_per_primitive() const {
+  return 3;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomTristrips::get_num_unused_vertices_per_primitive
 //       Access: Public, Virtual
@@ -142,6 +153,7 @@ decompose_impl() const {
   CPTA_int ends = get_ends();
 
   int num_vertices = get_num_vertices();
+  int num_unused = get_num_unused_vertices_per_primitive();
 
   // We need a slightly different algorithm for SM_flat_first_vertex
   // than for SM_flat_last_vertex, to preserve the key vertex in the
@@ -150,11 +162,11 @@ decompose_impl() const {
   if (get_shade_model() == SM_flat_first_vertex) {
     // Preserve the first vertex of each component triangle as the
     // first vertex of each generated triangle.
-    int vi = -2;
+    int vi = -num_unused;
     int li = 0;
     while (li < (int)ends.size()) {
       // Skip unused vertices between tristrips.
-      vi += 2;
+      vi += num_unused;
       int end = ends[li];
       nassertr(vi + 2 <= end, NULL);
       int v0 = get_vertex(vi);
@@ -192,11 +204,11 @@ decompose_impl() const {
   } else {
     // Preserve the last vertex of each component triangle as the
     // last vertex of each generated triangle.
-    int vi = -2;
+    int vi = -num_unused;
     int li = 0;
     while (li < (int)ends.size()) {
       // Skip unused vertices between tristrips.
-      vi += 2;
+      vi += num_unused;
       int end = ends[li];
       nassertr(vi + 2 <= end, NULL);
       int v0 = get_vertex(vi);

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

@@ -33,6 +33,7 @@ public:
   virtual PT(GeomPrimitive) make_copy() const;
   virtual PrimitiveType get_primitive_type() const;
   virtual int get_geom_rendering() const;
+  virtual int get_min_num_vertices_per_primitive() const;
   virtual int get_num_unused_vertices_per_primitive() const;
 
 public: