Преглед изворни кода

incorporate Clemens Pecinovsky's dynamic_merge optimization

David Rose пре 17 година
родитељ
комит
961b21115d

+ 10 - 3
panda/src/text/config_text.cxx

@@ -37,9 +37,16 @@ ConfigureFn(config_text) {
 ConfigVariableBool text_flatten
 ("text-flatten", true,
  PRC_DESC("Set this true to flatten text when it is generated, or false to "
-          "keep it as a deep hierarchy.  Unless you are debugging the text "
-          "interface, it is almost always a good idea to leave this at "
-          "its default, true."));
+          "keep it as a deep hierarchy.  Usually it's a performance "
+          "advantage to keep this true, but this also depends on the setting "
+          "of text-dynamic-merge.  See TextNode::set_flatten_flags()."));
+
+ConfigVariableBool text_dynamic_merge
+("text-dynamic-merge", true,
+ PRC_DESC("Set this true to merge generated glyphs into the GeomVertexData "
+          "as the text is assembled, or false to wait for the flatten "
+          "operation.  Usually it's a performance "
+          "advantage to keep this true.  See TextNode::set_flatten_flags()."));
 
 ConfigVariableInt text_anisotropic_degree
 ("text-anisotropic-degree", 1,

+ 1 - 0
panda/src/text/config_text.h

@@ -30,6 +30,7 @@ class DSearchPath;
 NotifyCategoryDecl(text, EXPCL_PANDA_TEXT, EXPTP_PANDA_TEXT);
 
 extern ConfigVariableBool text_flatten;
+extern ConfigVariableBool text_dynamic_merge;
 extern ConfigVariableInt text_anisotropic_degree;
 extern ConfigVariableInt text_texture_margin;
 extern ConfigVariableDouble text_poly_margin;

+ 47 - 0
panda/src/text/textAssembler.I

@@ -67,6 +67,28 @@ get_max_rows() const {
   return _max_rows;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::set_dynamic_merge
+//       Access: Published
+//  Description: Sets the dynamic_merge flag.  See
+//               TextNode::set_flatten_flags().
+////////////////////////////////////////////////////////////////////
+INLINE void TextAssembler::
+set_dynamic_merge(bool dynamic_merge) {
+  _dynamic_merge = dynamic_merge;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::get_dynamic_merge
+//       Access: Published
+//  Description: Returns the dynamic_merge flag.  See
+//               TextNode::set_flatten_flags().
+////////////////////////////////////////////////////////////////////
+INLINE bool TextAssembler::
+get_dynamic_merge() const {
+  return _dynamic_merge;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextAssembler::set_properties
 //       Access: Published
@@ -496,3 +518,28 @@ add_piece(Geom *geom, const RenderState *state) {
   piece._state = state;
   _pieces.push_back(piece);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollectorKey::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE TextAssembler::GeomCollectorKey::
+GeomCollectorKey(const RenderState *state, const GeomVertexFormat *format) :
+  _state(state),
+  _format(format)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollectorKey::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool TextAssembler::GeomCollectorKey::
+operator < (const TextAssembler::GeomCollectorKey &other) const {
+  if (_state != other._state) {
+    return _state < other._state;
+  }
+  return _format < other._format;
+}

+ 207 - 7
panda/src/text/textAssembler.cxx

@@ -22,6 +22,10 @@
 #include "textPropertiesManager.h"
 #include "textEncoder.h"
 #include "config_text.h"
+#include "geomTriangles.h"
+#include "geomLines.h"
+#include "geomPoints.h"
+#include "geomVertexReader.h"
 #include "geomVertexWriter.h"
 #include "geomLines.h"
 #include "geomVertexFormat.h"
@@ -84,7 +88,8 @@ TextAssembler::
 TextAssembler(TextEncoder *encoder) : 
   _encoder(encoder),
   _usage_hint(Geom::UH_static),
-  _max_rows(0)
+  _max_rows(0),
+  _dynamic_merge(text_dynamic_merge)
 {
   _initial_cprops = new ComputedProperties(TextProperties());
   clear();
@@ -105,7 +110,8 @@ TextAssembler(const TextAssembler &copy) :
   _next_row_ypos(copy._next_row_ypos),
   _encoder(copy._encoder),
   _usage_hint(copy._usage_hint),
-  _max_rows(copy._max_rows)
+  _max_rows(copy._max_rows),
+  _dynamic_merge(copy._dynamic_merge)
 {
 }
 
@@ -125,6 +131,7 @@ operator = (const TextAssembler &copy) {
   _encoder = copy._encoder;
   _usage_hint = copy._usage_hint;
   _max_rows = copy._max_rows;
+  _dynamic_merge = copy._dynamic_merge;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -555,7 +562,10 @@ assemble_text() {
   LMatrix4f shadow_xform;
 
   bool any_shadow = false;
-  
+
+  GeomCollectorMap geom_collector_map;
+  GeomCollectorMap geom_shadow_collector_map;
+
   PlacedGlyphs::const_iterator pgi;
   for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) {
     const GlyphPlacement *placement = (*pgi);
@@ -599,16 +609,24 @@ assemble_text() {
     // goes, while the place-text function just stomps on the
     // vertices.
     if (properties->has_shadow()) {
-      placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform);
+      if (_dynamic_merge) {
+        placement->assign_append_to(geom_shadow_collector_map, shadow_state, shadow_xform);
+      } else {
+        placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform);
+      }
 
       // Don't shadow the graphics.  That can result in duplication of
       // button objects, plus it looks weird.  If you want a shadowed
       // graphic, you can shadow it yourself before you add it.
       //placement->copy_graphic_to(shadow_node, shadow_state, shadow_xform);
-
       any_shadow = true;
     }
-    placement->assign_to(text_geom_node, text_state);
+	
+    if (_dynamic_merge) {
+      placement->assign_append_to(geom_collector_map, text_state, LMatrix4f::ident_mat());
+    } else {
+      placement->assign_to(text_geom_node, text_state);
+    }
     placement->copy_graphic_to(text_node, text_state, LMatrix4f::ident_mat());
     delete placement;
   }  
@@ -619,6 +637,20 @@ assemble_text() {
     // rendering order.
     parent_node->add_child(shadow_node);
   }
+
+  GeomCollectorMap::iterator gc;
+  for (gc = geom_collector_map.begin(); gc != geom_collector_map.end(); ++gc) {
+    (*gc).second.append_geom(text_geom_node, (*gc).first._state);
+  }
+
+  if (any_shadow) {
+    for (gc = geom_shadow_collector_map.begin(); 
+         gc != geom_shadow_collector_map.end();
+         ++gc) {
+      (*gc).second.append_geom(shadow_geom_node, (*gc).first._state);
+    }
+  }
+  
   parent_node->add_child(text_node);
 
   return parent_node;
@@ -1327,7 +1359,7 @@ assemble_row(TextAssembler::TextRow &row,
       line_height = max(line_height, frame[3] - frame[2]);
     } else {
       //[fabius] this is not the right place to calc line height (see below)
-//       line_height = max(line_height, font->get_line_height());
+      //       line_height = max(line_height, font->get_line_height());
     }
 
     if (character == ' ') {
@@ -2164,6 +2196,80 @@ assign_copy_to(GeomNode *geom_node, const RenderState *state,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::assign_append_to
+//       Access: Private
+//  Description: Puts the pieces of the GlyphPlacement in the
+//               indicated GeomNode.  This flavor will append the
+//               Geoms with the additional transform applied to the
+//               vertices.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GlyphPlacement::
+assign_append_to(GeomCollectorMap &geom_collector_map, 
+                 const RenderState *state,
+                 const LMatrix4f &extra_xform) const {
+  LMatrix4f new_xform = _xform * extra_xform;
+  Pieces::const_iterator pi;
+
+  int p, sp, s, e, i;
+  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
+    const Geom *geom = (*pi)._geom;
+    const GeomVertexData *vdata = geom->get_vertex_data();
+    CPT(RenderState) rs = (*pi)._state->compose(state);
+    GeomCollectorKey key(rs, vdata->get_format());
+
+    GeomCollectorMap::iterator mi = geom_collector_map.find(key);
+    if (mi == geom_collector_map.end()) {
+      mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first;
+    }
+    GeomCollector &geom_collector = (*mi).second;
+
+    // We use this map to keep track of vertex indices we have already
+    // added, so that we don't needlessly duplicate vertices into our
+    // output vertex data.
+    VertexIndexMap vimap;
+
+    for (p = 0; p < geom->get_num_primitives(); p++) {
+      CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose();
+
+      // Get a new GeomPrimitive of the corresponding type.
+      GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type());
+
+      // Walk through all of the components (e.g. triangles) of the
+      // primitive.
+      for (sp = 0; sp < primitive->get_num_primitives(); sp++) {
+        s = primitive->get_primitive_start(sp);
+        e = primitive->get_primitive_end(sp);
+
+        // Walk through all of the vertices in the component.
+        for (i = s; i < e; i++) {
+          int vi = primitive->get_vertex(i);
+
+          // Attempt to insert number "vi" into the map.
+          pair<VertexIndexMap::iterator, bool> added = vimap.insert(VertexIndexMap::value_type(vi, 0));
+          int new_vertex;
+          if (added.second) {
+            // The insert succeeded.  That means this is the first
+            // time we have encountered this vertex.
+            new_vertex = geom_collector.append_vertex(vdata, vi, new_xform);
+            // Update the map with the newly-created target vertex index.
+            (*(added.first)).second = new_vertex;
+
+          } else {
+            // The insert failed.  This means we have previously
+            // encountered this vertex, and we have already entered
+            // its target vertex index into the vimap.  Extract that
+            // vertex index, so we can reuse it.
+            new_vertex = (*(added.first)).second;
+          }
+          new_prim->add_vertex(new_vertex);
+        }
+        new_prim->close_primitive();
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextAssembler::GlyphPlacement::copy_graphic_to
 //       Access: Private
@@ -2186,3 +2292,97 @@ copy_graphic_to(PandaNode *node, const RenderState *state,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollector Constructor
+//       Access: Public
+//  Description: constructs the GeomCollector class 
+//               (Geom, GeomTriangles, vertexWriter, texcoordWriter..)
+////////////////////////////////////////////////////////////////////
+TextAssembler::GeomCollector::
+GeomCollector(const GeomVertexFormat *format) :
+  _vdata(new GeomVertexData("merged_geom", format, Geom::UH_static)),
+  _geom(new Geom(_vdata))
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollector Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextAssembler::GeomCollector::
+GeomCollector(const TextAssembler::GeomCollector &copy) :
+  _vdata(copy._vdata),
+  _geom(copy._geom)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollector::get_primitive
+//       Access: Public
+//  Description: Returns a GeomPrimitive of the appropriate type.  If
+//               one has not yet been created, returns a newly-created
+//               one; if one has previously been created of this type,
+//               returns the previously-created one.
+////////////////////////////////////////////////////////////////////
+GeomPrimitive *TextAssembler::GeomCollector::
+get_primitive(TypeHandle prim_type) {
+  if (prim_type == GeomTriangles::get_class_type()) {
+    if (_triangles == (GeomPrimitive *)NULL) {
+      _triangles = new GeomTriangles(Geom::UH_static);
+      _geom->add_primitive(_triangles);
+    }
+    return _triangles;
+
+  } else if (prim_type == GeomLines::get_class_type()) {
+    if (_lines == (GeomPrimitive *)NULL) {
+      _lines = new GeomLines(Geom::UH_static);
+      _geom->add_primitive(_lines);
+    }
+    return _lines;
+
+  } else if (prim_type == GeomPoints::get_class_type()) {
+    if (_points == (GeomPrimitive *)NULL) {
+      _points = new GeomPoints(Geom::UH_static);
+      _geom->add_primitive(_points);
+    }
+    return _points;
+  }
+
+  nassertr(false, false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollector::append_vertex
+//       Access: Public
+//  Description: Adds one vertex to the GeomVertexData.
+//               Returns the row number of the added vertex.
+////////////////////////////////////////////////////////////////////
+int TextAssembler::GeomCollector::
+append_vertex(const GeomVertexData *orig_vdata, int orig_row,
+              const LMatrix4f &xform) {
+  int new_row = _vdata->get_num_rows();
+  _vdata->copy_row_from(new_row, orig_vdata, orig_row, Thread::get_current_thread());
+
+  GeomVertexRewriter vertex_rewriter(_vdata, InternalName::get_vertex());
+  vertex_rewriter.set_row(new_row);
+  LPoint3f point = vertex_rewriter.get_data3f();
+  vertex_rewriter.set_data3f(point * xform);
+
+  return new_row;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GeomCollector::append_geom
+//       Access: Public
+//  Description: closes the geomTriangles and appends the geom to 
+//               the given GeomNode
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GeomCollector::
+append_geom(GeomNode *geom_node, const RenderState *state) {
+  if (_geom->get_num_primitives() > 0) {
+    geom_node->add_geom(_geom, state);
+  }
+}
+

+ 41 - 0
panda/src/text/textAssembler.h

@@ -25,6 +25,10 @@
 #include "geom.h"
 #include "textPropertiesManager.h"
 #include "textEncoder.h"
+#include "geomVertexRewriter.h"
+
+#include "pmap.h"
+
 
 class TextEncoder;
 class TextGraphic;
@@ -54,6 +58,9 @@ PUBLISHED:
   INLINE void set_max_rows(int max_rows);
   INLINE int get_max_rows() const;
 
+  INLINE void set_dynamic_merge(bool dynamic_merge);
+  INLINE bool get_dynamic_merge() const;
+
   INLINE void set_properties(const TextProperties &properties);
   INLINE const TextProperties &get_properties() const;
 
@@ -177,6 +184,36 @@ private:
   };
   typedef pvector<Piece> Pieces;
 
+  class GeomCollectorKey {
+  public:
+    INLINE GeomCollectorKey(const RenderState *state, const GeomVertexFormat *format);
+    INLINE bool operator < (const GeomCollectorKey &other) const;
+
+    CPT(RenderState) _state;
+    CPT(GeomVertexFormat) _format;
+  };
+
+  typedef pmap<int, int> VertexIndexMap;
+
+  class GeomCollector {
+  public:
+    GeomCollector(const GeomVertexFormat *format);
+    GeomCollector(const GeomCollector &copy);
+
+    GeomPrimitive *get_primitive(TypeHandle prim_type);
+    int append_vertex(const GeomVertexData *orig_vdata, int orig_row,
+                      const LMatrix4f &xform);
+    void append_geom(GeomNode *geom_node, const RenderState *state);
+
+  private:
+    PT(GeomVertexData) _vdata;
+    PT(Geom) _geom;
+    PT(GeomTriangles) _triangles;
+    PT(GeomLines) _lines;
+    PT(GeomPoints) _points;
+  };
+  typedef pmap<GeomCollectorKey, GeomCollector> GeomCollectorMap;
+
   class GlyphPlacement {
   public:
     INLINE void add_piece(Geom *geom, const RenderState *state);
@@ -185,6 +222,9 @@ private:
     void assign_to(GeomNode *geom_node, const RenderState *state) const;
     void assign_copy_to(GeomNode *geom_node, const RenderState *state, 
                         const LMatrix4f &extra_xform) const;
+
+    void assign_append_to(GeomCollectorMap &geom_collector_map, const RenderState *state,
+                          const LMatrix4f &extra_xform) const;
     void copy_graphic_to(PandaNode *node, const RenderState *state,
                          const LMatrix4f &extra_xform) const;
 
@@ -263,6 +303,7 @@ private:
   TextEncoder *_encoder;
   Geom::UsageHint _usage_hint;
   int _max_rows;
+  bool _dynamic_merge;
 };
 
 #include "textAssembler.I"

+ 59 - 0
panda/src/text/textNode.I

@@ -680,6 +680,65 @@ get_usage_hint() const {
   return _usage_hint;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::set_flatten_flags
+//       Access: Published
+//  Description: Sets the flatten flags.  This should be a union of
+//               the TextNode::FlattenFlags options.  This controls
+//               the degree of flattening performed on the TextNode's
+//               internal geometry (i.e. the scene graph returned by
+//               generate()) each time the text is changed.  In
+//               general, more flattening means a more optimal result,
+//               but it will take more time to generate.
+//
+//               The choice may be any of these three:
+//
+//               FF_none - No flatten operation is called.  The
+//               letters are left as independent Geoms.
+//
+//               FF_light - A flatten_light() operation is called.
+//               The attributes are applied to the vertices, but no
+//               nodes are removed.
+//
+//               FF_medium - A flatten_medium() operation is called.
+//               The attributes are applied to the vertices, and a few
+//               trivial nodes are removed.
+//
+//               FF_strong - A flatten_strong() operation is called.
+//               The attributes are applied to the vertices, and the
+//               resulting nodes are aggressively combined into as few
+//               nodes as possible.
+//
+//               In addition to the above choices, you may optionally
+//               include the following flag:
+//
+//               FF_dynamic_merge - Copy the geoms into a single
+//               GeomVertexData as we go, instead of relying on the
+//               flatten operation at the end.  This pre-flattens the
+//               text considerably, and may obviate the need for
+//               flatten altogether; it also tends to improve
+//               performance considerably even if you do call flatten.
+//               However, it is not as fast as not calling flatten at
+//               all.
+//
+//               The default is taken from the text-flatten and
+//               text-dynamic-merge config variables.
+////////////////////////////////////////////////////////////////////
+INLINE void TextNode::
+set_flatten_flags(int flatten_flags) {
+  _flatten_flags = flatten_flags;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::get_flatten_flags
+//       Access: Published
+//  Description: Returns the flatten flags.  See set_flatten_flags().
+////////////////////////////////////////////////////////////////////
+INLINE int TextNode::
+get_flatten_flags() const {
+  return _flatten_flags;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_font
 //       Access: Published

+ 21 - 7
panda/src/text/textNode.cxx

@@ -66,6 +66,13 @@ TextNode(const string &name) : PandaNode(name) {
   _flags = 0;
   _max_rows = 0;
   _usage_hint = GeomEnums::UH_static;
+  _flatten_flags = 0;
+  if (text_flatten) {
+    _flatten_flags |= FF_strong;
+  }
+  if (text_dynamic_merge) {
+    _flatten_flags |= FF_dynamic_merge;
+  }
 
   if (text_small_caps) {
     set_small_caps(true);
@@ -355,7 +362,12 @@ generate() {
   _lr3d.set(0.0f, 0.0f, 0.0f);
 
   // Now build a new sub-tree for all the text components.
-  PT(PandaNode) root = new PandaNode(get_text());
+  string name = get_text();
+  size_t newline = name.find('\n');
+  if (newline != string::npos) {
+    name = name.substr(0, newline);
+  }
+  PT(PandaNode) root = new PandaNode(name);
 
   if (!has_text()) {
     return root;
@@ -383,6 +395,7 @@ generate() {
   assembler.set_properties(*this);
   assembler.set_max_rows(_max_rows);
   assembler.set_usage_hint(_usage_hint);
+  assembler.set_dynamic_merge((_flatten_flags & FF_dynamic_merge) != 0);
   bool all_set = assembler.set_wtext(wtext);
   if (all_set) {
     // No overflow.
@@ -419,12 +432,13 @@ generate() {
   // Now flatten our hierarchy to get rid of the transforms we put in,
   // applying them to the vertices.
 
-  if (text_flatten) {
-    SceneGraphReducer gr;
-    gr.apply_attribs(root);
-    gr.flatten(root, ~SceneGraphReducer::CS_within_radius);
-    gr.collect_vertex_data(root);
-    gr.unify(root, true);
+  NodePath root_np(root);
+  if (_flatten_flags & FF_strong) {
+    root_np.flatten_strong();
+  } else if (_flatten_flags & FF_medium) {
+    root_np.flatten_medium();
+  } else if (_flatten_flags & FF_light) {
+    root_np.flatten_light();
   }
 
   // Now deal with the decorations.

+ 13 - 0
panda/src/text/textNode.h

@@ -60,6 +60,14 @@ protected:
 PUBLISHED:
   ~TextNode();
 
+  enum FlattenFlags {
+    FF_none          = 0x0000,
+    FF_light         = 0x0001,
+    FF_medium        = 0x0002,
+    FF_strong        = 0x0004,
+    FF_dynamic_merge = 0x0008,
+  };
+
   INLINE float get_line_height() const;
 
   INLINE void set_max_rows(int max_rows);
@@ -124,6 +132,9 @@ PUBLISHED:
   INLINE void set_usage_hint(Geom::UsageHint usage_hint);
   INLINE Geom::UsageHint get_usage_hint() const;
 
+  INLINE void set_flatten_flags(int flatten_flags);
+  INLINE int get_flatten_flags() const;
+
   // These methods are inherited from TextProperties, but we override
   // here so we can flag the TextNode as dirty when they have been
   // changed.
@@ -291,6 +302,8 @@ private:
   int _flags;
   int _max_rows;
   GeomEnums::UsageHint _usage_hint;
+  int _flatten_flags;
+  bool _dynamic_merge;
   float _frame_width;
   float _card_border_size;
   float _card_border_uv_portion;