Panda3D
sceneGraphAnalyzer.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file sceneGraphAnalyzer.cxx
10  * @author drose
11  * @date 2000-07-02
12  */
13 
14 #include "sceneGraphAnalyzer.h"
15 #include "config_pgraph.h"
16 
17 #include "indent.h"
18 #include "lodNode.h"
19 #include "geomNode.h"
20 #include "geomVertexData.h"
21 #include "geom.h"
22 #include "geomPrimitive.h"
23 #include "geomPoints.h"
24 #include "geomLines.h"
25 #include "geomLinestrips.h"
26 #include "geomTriangles.h"
27 #include "geomTristrips.h"
28 #include "geomTrifans.h"
29 #include "geomPatches.h"
30 #include "transformState.h"
31 #include "textureAttrib.h"
32 #include "pta_ushort.h"
33 #include "geomVertexReader.h"
34 
35 /**
36  *
37  */
38 SceneGraphAnalyzer::
39 SceneGraphAnalyzer() {
40  _lod_mode = LM_all;
41  clear();
42 }
43 
44 /**
45  *
46  */
47 SceneGraphAnalyzer::
48 ~SceneGraphAnalyzer() {
49 }
50 
51 /**
52  * Resets all of the data in the analyzer in preparation for a new run.
53  */
55 clear() {
56  _nodes.clear();
57  _vdatas.clear();
58  _vformats.clear();
59  _vadatas.clear();
60  _unique_vdatas.clear();
61  _unique_vadatas.clear();
62  _textures.clear();
63 
64  _num_nodes = 0;
65  _num_instances = 0;
66  _num_transforms = 0;
67  _num_nodes_with_attribs = 0;
68  _num_lod_nodes = 0;
69  _num_geom_nodes = 0;
70  _num_geoms = 0;
71  _num_geom_vertex_datas = 0;
72  _num_geom_vertex_formats = 0;
73  _vertex_data_size = 0;
74  _prim_data_size = 0;
75 
76  _num_vertices = 0;
77  _num_vertices_64 = 0;
78  _num_normals = 0;
79  _num_colors = 0;
80  _num_texcoords = 0;
81  _num_tris = 0;
82  _num_lines = 0;
83  _num_points = 0;
84  _num_patches = 0;
85 
86  _num_individual_tris = 0;
87  _num_tristrips = 0;
88  _num_triangles_in_strips = 0;
89  _num_trifans = 0;
90  _num_triangles_in_fans = 0;
91  _num_vertices_in_patches = 0;
92 
93  _texture_bytes = 0;
94 
95  _num_long_normals = 0;
96  _num_short_normals = 0;
97  _total_normal_length = 0.0f;
98 }
99 
100 /**
101  * Adds a new node to the set of data for analysis. Normally, this would only
102  * be called once, and passed the top of the scene graph, but it's possible to
103  * repeatedly pass in subgraphs to get an analysis of all the graphs together.
104  */
106 add_node(PandaNode *node) {
107  collect_statistics(node, false);
108 }
109 
110 /**
111  * Describes all the data collected.
112  */
114 write(std::ostream &out, int indent_level) const {
115  indent(out, indent_level)
116  << _num_nodes << " total nodes (including "
117  << _num_instances << " instances); " << _num_lod_nodes << " LODNodes.\n";
118 
119  indent(out, indent_level)
120  << _num_transforms << " transforms";
121 
122  if (_num_nodes != 0) {
123  out << "; " << 100 * _num_nodes_with_attribs / _num_nodes
124  << "% of nodes have some render attribute.";
125  }
126  out << "\n";
127 
128  indent(out, indent_level)
129  << _num_geoms << " Geoms, with " << _num_geom_vertex_datas
130  << " GeomVertexDatas and " << _num_geom_vertex_formats
131  << " GeomVertexFormats, appear on " << _num_geom_nodes
132  << " GeomNodes.\n";
133 
134  indent(out, indent_level);
135  if (_num_vertices_64 != 0) {
136  out << _num_vertices_64 << " 64-bit vertices, ";
137  if (_num_vertices != _num_vertices_64) {
138  out << _num_vertices - _num_vertices_64 << " 32-bit vertices, ";
139  }
140  } else {
141  out << _num_vertices << " vertices, ";
142  }
143 
144  out << _num_normals << " normals, "
145  << _num_colors << " colors, "
146  << _num_texcoords << " texture coordinates.\n";
147 
148  if (_num_long_normals != 0 || _num_short_normals != 0) {
149  indent(out, indent_level)
150  << _num_long_normals << " normals are too long, "
151  << _num_short_normals << " are too short. Average normal length is "
152  << _total_normal_length / (PN_stdfloat)_num_normals << "\n";
153  }
154 
155  indent(out, indent_level)
156  << "GeomVertexData arrays occupy " << (_vertex_data_size + 1023) / 1024
157  << "K memory.\n";
158 
159  indent(out, indent_level)
160  << "GeomPrimitive arrays occupy " << (_prim_data_size + 1023) / 1024
161  << "K memory.\n";
162 
163  int unreferenced_vertices = 0;
164  VDatas::const_iterator vdi;
165  for (vdi = _vdatas.begin(); vdi != _vdatas.end(); ++vdi) {
166  CPT(GeomVertexData) vdata = (*vdi).first;
167  const VDataTracker &tracker = (*vdi).second;
168  int num_unreferenced = vdata->get_num_rows() - tracker._referenced_vertices.get_num_on_bits();
169  nassertv(num_unreferenced >= 0);
170  unreferenced_vertices += num_unreferenced;
171  }
172  if (unreferenced_vertices != 0) {
173  indent(out, indent_level)
174  << unreferenced_vertices << " vertices are unreferenced by any GeomPrimitives.\n";
175  }
176  if (_unique_vdatas.size() != _vdatas.size()) {
177  indent(out, indent_level)
178  << _vdatas.size() - _unique_vdatas.size()
179  << " GeomVertexDatas are redundantly duplicated\n";
180  }
181  if (_unique_vadatas.size() != _vadatas.size()) {
182  int wasted_bytes = 0;
183 
184  UniqueVADatas::const_iterator uvai;
185  for (uvai = _unique_vadatas.begin();
186  uvai != _unique_vadatas.end();
187  ++uvai) {
188  const GeomVertexArrayData *gvad = (*uvai).first;
189  int dup_count = (*uvai).second;
190  if (dup_count > 1) {
191  wasted_bytes += (dup_count - 1) * gvad->get_data_size_bytes();
192  }
193  }
194  indent(out, indent_level)
195  << _vadatas.size() - _unique_vadatas.size()
196  << " GeomVertexArrayDatas are redundant, wasting "
197  << (wasted_bytes + 1023) / 1024 << "K.\n";
198  }
199  if (_unique_prim_vadatas.size() != _prim_vadatas.size()) {
200  int wasted_bytes = 0;
201 
202  UniqueVADatas::const_iterator uvai;
203  for (uvai = _unique_prim_vadatas.begin();
204  uvai != _unique_prim_vadatas.end();
205  ++uvai) {
206  const GeomVertexArrayData *gvad = (*uvai).first;
207  int dup_count = (*uvai).second;
208  if (dup_count > 1) {
209  wasted_bytes += (dup_count - 1) * gvad->get_data_size_bytes();
210  }
211  }
212  indent(out, indent_level)
213  << _prim_vadatas.size() - _unique_prim_vadatas.size()
214  << " GeomPrimitive arrays are redundant, wasting "
215  << (wasted_bytes + 1023) / 1024 << "K.\n";
216  }
217 
218  indent(out, indent_level)
219  << _num_tris << " triangles:\n";
220  indent(out, indent_level + 2)
221  << _num_triangles_in_strips
222  << " of these are on " << _num_tristrips << " tristrips";
223  if (_num_tristrips != 0) {
224  out << " ("
225  << (double)_num_triangles_in_strips / (double)_num_tristrips
226  << " average tris per strip)";
227  }
228  out << ".\n";
229 
230  if (_num_trifans != 0) {
231  indent(out, indent_level + 2)
232  << _num_triangles_in_fans
233  << " of these are on " << _num_trifans << " trifans";
234  if (_num_trifans != 0) {
235  out << " ("
236  << (double)_num_triangles_in_fans / (double)_num_trifans
237  << " average tris per fan)";
238  }
239  out << ".\n";
240  }
241 
242  indent(out, indent_level + 2)
243  << _num_individual_tris
244  << " of these are independent triangles.\n";
245 
246  if (_num_patches != 0) {
247  indent(out, indent_level)
248  << _num_patches << " patches ("
249  << (double)_num_vertices_in_patches / (double)_num_patches
250  << " average verts per patch).\n";
251  }
252 
253  if (_num_lines != 0 || _num_points != 0) {
254  indent(out, indent_level)
255  << _num_lines << " lines, " << _num_points << " points.\n";
256  }
257 
258  indent(out, indent_level)
259  << _textures.size() << " textures, estimated minimum "
260  << (_texture_bytes + 1023) / 1024 << "K texture memory required.\n";
261 }
262 
263 /**
264  * Recursively visits each node, counting up the statistics.
265  */
266 void SceneGraphAnalyzer::
267 collect_statistics(PandaNode *node, bool under_instance) {
268  _num_nodes++;
269 
270  if (!under_instance) {
271  Nodes::iterator ni = _nodes.find(node);
272  if (ni == _nodes.end()) {
273  // This is the first time this node has been encountered.
274  _nodes.insert(Nodes::value_type(node, 1));
275  } else {
276  // This node has been encountered before; that makes it an instance.
277  (*ni).second++;
278  _num_instances++;
279  under_instance = true;
280  }
281  }
282 
283  if (!node->get_state()->is_empty()) {
284  _num_nodes_with_attribs++;
285  const RenderAttrib *attrib =
286  node->get_attrib(TextureAttrib::get_class_slot());
287  if (attrib != nullptr) {
288  const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
289  for (int i = 0; i < ta->get_num_on_stages(); i++) {
290  collect_statistics(ta->get_on_texture(ta->get_on_stage(i)));
291  }
292  }
293  }
294  if (!node->get_transform()->is_identity()) {
295  _num_transforms++;
296  }
297 
298  if (node->is_geom_node()) {
299  collect_statistics(DCAST(GeomNode, node));
300  }
301 
302  if (node->is_lod_node()) {
303  LODNode *lod_node = DCAST(LODNode, node);
304  ++_num_lod_nodes;
305 
306  switch (_lod_mode) {
307  case LM_lowest:
308  case LM_highest:
309  {
310  int sw = (_lod_mode == LM_lowest) ? lod_node->get_lowest_switch() : lod_node->get_highest_switch();
311  if (sw >= 0 && sw < node->get_num_children()) {
312  PandaNode *child = node->get_child(sw);
313  collect_statistics(child, under_instance);
314  }
315  return;
316  }
317 
318  case LM_none:
319  return;
320 
321  case LM_all:
322  // fall through to the loop below.
323  break;
324  }
325  }
326 
327  int num_children = node->get_num_children();
328  for (int i = 0; i < num_children; i++) {
329  PandaNode *child = node->get_child(i);
330  collect_statistics(child, under_instance);
331  }
332 }
333 
334 /**
335  * Recursively visits each node, counting up the statistics.
336  */
337 void SceneGraphAnalyzer::
338 collect_statistics(GeomNode *geom_node) {
339  nassertv(geom_node != nullptr);
340 
341  ++_num_geom_nodes;
342 
343  int num_geoms = geom_node->get_num_geoms();
344  _num_geoms += num_geoms;
345 
346  for (int i = 0; i < num_geoms; i++) {
347  const Geom *geom = geom_node->get_geom(i);
348  collect_statistics(geom);
349 
350  const RenderState *geom_state = geom_node->get_geom_state(i);
351 
352  const RenderAttrib *attrib =
353  geom_state->get_attrib(TextureAttrib::get_class_slot());
354  if (attrib != nullptr) {
355  const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
356  for (int i = 0; i < ta->get_num_on_stages(); i++) {
357  collect_statistics(ta->get_on_texture(ta->get_on_stage(i)));
358  }
359  }
360  }
361 }
362 
363 /**
364  * Recursively visits each node, counting up the statistics.
365  */
366 void SceneGraphAnalyzer::
367 collect_statistics(const Geom *geom) {
368  CPT(GeomVertexData) vdata = geom->get_vertex_data();
369  std::pair<VDatas::iterator, bool> result = _vdatas.insert(VDatas::value_type(vdata, VDataTracker()));
370  if (result.second) {
371  // This is the first time we've encountered this vertex data.
372  ++_num_geom_vertex_datas;
373 
374  CPT(GeomVertexFormat) vformat = vdata->get_format();
375  bool format_inserted = _vformats.insert(vformat).second;
376  if (format_inserted) {
377  // This is the first time we've encountered this vertex format.
378  ++_num_geom_vertex_formats;
379  }
380 
381  int &dup_count = (*(_unique_vdatas.insert(UniqueVDatas::value_type(vdata, 0)).first)).second;
382  ++dup_count;
383 
384  int num_rows = vdata->get_num_rows();
385  const GeomVertexFormat *format = vdata->get_format();
386  if (format->has_column(InternalName::get_vertex())) {
387  _num_vertices += num_rows;
388  const GeomVertexColumn *vcolumn = format->get_column(InternalName::get_vertex());
389  if (vcolumn->get_numeric_type() == GeomEnums::NT_float64) {
390  _num_vertices_64 += num_rows;
391  }
392  }
393  if (format->has_column(InternalName::get_normal())) {
394  _num_normals += num_rows;
395  GeomVertexReader rnormal(vdata, InternalName::get_normal());
396  while (!rnormal.is_at_end()) {
397  LVector3f normal = rnormal.get_data3f();
398  float length = normal.length();
399  if (IS_NEARLY_EQUAL(length, 1.0f)) {
400  // Correct length normal.
401  } else if (length > 1.0f) {
402  ++_num_long_normals;
403  } else { // length < 1.0f
404  ++_num_short_normals;
405  }
406  _total_normal_length += length;
407  }
408  }
409  if (format->has_column(InternalName::get_color())) {
410  _num_colors += num_rows;
411  }
412  int num_texcoords = format->get_num_texcoords();
413  _num_texcoords += num_rows * num_texcoords;
414 
415  int num_arrays = vdata->get_num_arrays();
416  for (int i = 0; i < num_arrays; ++i) {
417  collect_statistics(vdata->get_array(i));
418  }
419  }
420  VDataTracker &tracker = (*(result.first)).second;
421 
422  // Now consider the primitives in the Geom.
423  int num_primitives = geom->get_num_primitives();
424  for (int i = 0; i < num_primitives; ++i) {
425  CPT(GeomPrimitive) prim = geom->get_primitive(i);
426 
427  int num_vertices = prim->get_num_vertices();
428  int strip_cut_index = prim->get_strip_cut_index();
429  for (int vi = 0; vi < num_vertices; ++vi) {
430  int index = prim->get_vertex(vi);
431  if (index != strip_cut_index) {
432  tracker._referenced_vertices.set_bit(index);
433  }
434  }
435 
436  if (prim->is_indexed()) {
437  collect_prim_statistics(prim->get_vertices());
438  if (prim->is_composite()) {
439  collect_statistics(prim->get_mins());
440  collect_statistics(prim->get_maxs());
441  }
442  }
443 
444  if (prim->is_of_type(GeomPoints::get_class_type())) {
445  _num_points += prim->get_num_primitives();
446 
447  } else if (prim->is_of_type(GeomLines::get_class_type())) {
448  _num_lines += prim->get_num_primitives();
449 
450  } else if (prim->is_of_type(GeomLinestrips::get_class_type())) {
451  _num_lines += prim->get_num_faces();
452 
453  } else if (prim->is_of_type(GeomTriangles::get_class_type())) {
454  _num_tris += prim->get_num_primitives();
455  _num_individual_tris += prim->get_num_primitives();
456 
457  } else if (prim->is_of_type(GeomTristrips::get_class_type())) {
458  _num_tris += prim->get_num_faces();
459  _num_tristrips += prim->get_num_primitives();
460  _num_triangles_in_strips += prim->get_num_faces();
461 
462  } else if (prim->is_of_type(GeomTrifans::get_class_type())) {
463  _num_tris += prim->get_num_faces();
464  _num_trifans += prim->get_num_primitives();
465  _num_triangles_in_fans += prim->get_num_faces();
466 
467  } else if (prim->is_of_type(GeomPatches::get_class_type())) {
468  _num_patches += prim->get_num_primitives();
469  _num_vertices_in_patches += prim->get_num_vertices();
470 
471  } else {
472  pgraph_cat.warning()
473  << "Unknown GeomPrimitive type in SceneGraphAnalyzer: "
474  << prim->get_type() << "\n";
475  }
476  }
477 }
478 
479 /**
480  * Recursively visits each node, counting up the statistics.
481  */
482 void SceneGraphAnalyzer::
483 collect_statistics(Texture *texture) {
484  nassertv(texture != nullptr);
485 
486  Textures::iterator ti = _textures.find(texture);
487  if (ti == _textures.end()) {
488  // This is the first time this texture has been encountered.
489  _textures.insert(Textures::value_type(texture, 1));
490 
491  // Attempt to guess how many bytes of texture memory this one requires.
492  size_t bytes =
493  texture->get_x_size() * texture->get_y_size() *
494  texture->get_num_components() * texture->get_component_width();
495 
496  if (texture->uses_mipmaps()) {
497  bytes *= 4/3;
498  }
499 
500  _texture_bytes += bytes;
501 
502  } else {
503  // This texture has been encountered before; don't count it again.
504  (*ti).second++;
505  }
506 }
507 
508 /**
509  * Recursively visits each node, counting up the statistics.
510  */
511 void SceneGraphAnalyzer::
512 collect_statistics(const GeomVertexArrayData *vadata) {
513  nassertv(vadata != nullptr);
514  bool inserted = _vadatas.insert(vadata).second;
515  if (inserted) {
516  // This is the first time we've encountered this vertex array.
517  _vertex_data_size += vadata->get_data_size_bytes();
518  int &dup_count = (*(_unique_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
519  ++dup_count;
520  }
521 }
522 
523 /**
524  * Recursively visits each node, counting up the statistics. This one records
525  * the vertex index array associated with a GeomPrimitive, as opposed to the
526  * vertex data array, component of a GeomVertexData.
527  */
528 void SceneGraphAnalyzer::
529 collect_prim_statistics(const GeomVertexArrayData *vadata) {
530  nassertv(vadata != nullptr);
531  bool inserted = _prim_vadatas.insert(vadata).second;
532  if (inserted) {
533  // This is the first time we've encountered this vertex array.
534  _prim_data_size += vadata->get_data_size_bytes();
535  int &dup_count = (*(_unique_prim_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
536  ++dup_count;
537  }
538 }
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
get_num_geoms
Returns the number of geoms in the node.
Definition: geomNode.h:71
get_geom_state
Returns the RenderState associated with the nth geom of the node.
Definition: geomNode.h:75
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
This is the data for one array of a GeomVertexData structure.
get_data_size_bytes
Returns the number of bytes stored in the array.
This defines how a single column is interleaved within a vertex array stored within a Geom.
NumericType get_numeric_type() const
Returns the token representing the numeric type of the data storage.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
get_num_texcoords
Returns the number of columns within the format that represent texture coordinates.
has_column
Returns true if the format has the named column, false otherwise.
get_column
Returns the ith column of the specification, across all arrays.
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
A Level-of-Detail node.
Definition: lodNode.h:28
get_lowest_switch
Returns the index number of the child with the lowest level of detail; that is, the one that is desig...
Definition: lodNode.h:80
get_highest_switch
Returns the index number of the child with the highest level of detail; that is, the one that is desi...
Definition: lodNode.h:81
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
virtual bool is_geom_node() const
A simple downcast check.
Definition: pandaNode.cxx:2062
get_child
Returns the nth child node of this node.
Definition: pandaNode.h:124
virtual bool is_lod_node() const
A simple downcast check.
Definition: pandaNode.cxx:2074
get_num_children
Returns the number of child nodes this node has.
Definition: pandaNode.h:124
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
void add_node(PandaNode *node)
Adds a new node to the set of data for analysis.
void write(std::ostream &out, int indent_level=0) const
Describes all the data collected.
void clear()
Resets all of the data in the analyzer in preparation for a new run.
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.h:55
get_on_texture
Returns the texture associated with the indicated stage, or NULL if no texture is associated.
Definition: textureAttrib.h:69
get_on_stage
Returns the nth stage turned on by the attribute, sorted in render order.
Definition: textureAttrib.h:55
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
get_component_width
Returns the number of bytes stored for each color component of a texel.
Definition: texture.h:364
get_y_size
Returns the height of the texture image in texels.
Definition: texture.h:346
get_num_components
Returns the number of color components for each texel of the texture image.
Definition: texture.h:363
bool uses_mipmaps() const
Returns true if the minfilter settings on this texture indicate the use of mipmapping,...
Definition: texture.I:1127
get_x_size
Returns the width of the texture image in texels.
Definition: texture.h:342
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.