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  */
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  for (int vi = 0; vi < num_vertices; ++vi) {
429  tracker._referenced_vertices.set_bit(prim->get_vertex(vi));
430  }
431 
432  if (prim->is_indexed()) {
433  collect_prim_statistics(prim->get_vertices());
434  if (prim->is_composite()) {
435  collect_statistics(prim->get_mins());
436  collect_statistics(prim->get_maxs());
437  }
438  }
439 
440  if (prim->is_of_type(GeomPoints::get_class_type())) {
441  _num_points += prim->get_num_primitives();
442 
443  } else if (prim->is_of_type(GeomLines::get_class_type())) {
444  _num_lines += prim->get_num_primitives();
445 
446  } else if (prim->is_of_type(GeomLinestrips::get_class_type())) {
447  _num_lines += prim->get_num_faces();
448 
449  } else if (prim->is_of_type(GeomTriangles::get_class_type())) {
450  _num_tris += prim->get_num_primitives();
451  _num_individual_tris += prim->get_num_primitives();
452 
453  } else if (prim->is_of_type(GeomTristrips::get_class_type())) {
454  _num_tris += prim->get_num_faces();
455  _num_tristrips += prim->get_num_primitives();
456  _num_triangles_in_strips += prim->get_num_faces();
457 
458  } else if (prim->is_of_type(GeomTrifans::get_class_type())) {
459  _num_tris += prim->get_num_faces();
460  _num_trifans += prim->get_num_primitives();
461  _num_triangles_in_fans += prim->get_num_faces();
462 
463  } else if (prim->is_of_type(GeomPatches::get_class_type())) {
464  _num_patches += prim->get_num_primitives();
465  _num_vertices_in_patches += prim->get_num_vertices();
466 
467  } else {
468  pgraph_cat.warning()
469  << "Unknown GeomPrimitive type in SceneGraphAnalyzer: "
470  << prim->get_type() << "\n";
471  }
472  }
473 }
474 
475 /**
476  * Recursively visits each node, counting up the statistics.
477  */
478 void SceneGraphAnalyzer::
479 collect_statistics(Texture *texture) {
480  nassertv(texture != nullptr);
481 
482  Textures::iterator ti = _textures.find(texture);
483  if (ti == _textures.end()) {
484  // This is the first time this texture has been encountered.
485  _textures.insert(Textures::value_type(texture, 1));
486 
487  // Attempt to guess how many bytes of texture memory this one requires.
488  size_t bytes =
489  texture->get_x_size() * texture->get_y_size() *
490  texture->get_num_components() * texture->get_component_width();
491 
492  if (texture->uses_mipmaps()) {
493  bytes *= 4/3;
494  }
495 
496  _texture_bytes += bytes;
497 
498  } else {
499  // This texture has been encountered before; don't count it again.
500  (*ti).second++;
501  }
502 }
503 
504 /**
505  * Recursively visits each node, counting up the statistics.
506  */
507 void SceneGraphAnalyzer::
508 collect_statistics(const GeomVertexArrayData *vadata) {
509  nassertv(vadata != nullptr);
510  bool inserted = _vadatas.insert(vadata).second;
511  if (inserted) {
512  // This is the first time we've encountered this vertex array.
513  _vertex_data_size += vadata->get_data_size_bytes();
514  int &dup_count = (*(_unique_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
515  ++dup_count;
516  }
517 }
518 
519 /**
520  * Recursively visits each node, counting up the statistics. This one records
521  * the vertex index array associated with a GeomPrimitive, as opposed to the
522  * vertex data array, component of a GeomVertexData.
523  */
524 void SceneGraphAnalyzer::
525 collect_prim_statistics(const GeomVertexArrayData *vadata) {
526  nassertv(vadata != nullptr);
527  bool inserted = _prim_vadatas.insert(vadata).second;
528  if (inserted) {
529  // This is the first time we've encountered this vertex array.
530  _prim_data_size += vadata->get_data_size_bytes();
531  int &dup_count = (*(_unique_prim_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
532  ++dup_count;
533  }
534 }
void write(std::ostream &out, int indent_level=0) const
Describes all the data collected.
get_geom_state
Returns the RenderState associated with the nth geom of the node.
Definition: geomNode.h:75
get_y_size
Returns the height of the texture image in texels.
Definition: texture.h:338
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
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.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.h:55
NumericType get_numeric_type() const
Returns the token representing the numeric type of the data storage.
get_on_stage
Returns the nth stage turned on by the attribute, sorted in render order.
Definition: textureAttrib.h:55
void add_node(PandaNode *node)
Adds a new node to the set of data for analysis.
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
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear()
Resets all of the data in the analyzer in preparation for a new run.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This defines how a single column is interleaved within a vertex array stored within a Geom.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
get_num_texcoords
Returns the number of columns within the format that represent texture coordinates.
get_num_children
Returns the number of child nodes this node has.
Definition: pandaNode.h:124
bool uses_mipmaps() const
Returns true if the minfilter settings on this texture indicate the use of mipmapping,...
Definition: texture.I:1127
A Level-of-Detail node.
Definition: lodNode.h:28
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
get_column
Returns the ith column of the specification, across all arrays.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
get_num_components
Returns the number of color components for each texel of the texture image.
Definition: texture.h:355
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
Definition: geom.h:54
get_component_width
Returns the number of bytes stored for each color component of a texel.
Definition: texture.h:356
get_data_size_bytes
Returns the number of bytes stored in the array.
virtual bool is_lod_node() const
A simple downcast check.
Definition: pandaNode.cxx:2080
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
This class defines the physical layout of the vertex data stored within a Geom.
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
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.
get_num_geoms
Returns the number of geoms in the node.
Definition: geomNode.h:71
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
get_child
Returns the nth child node of this node.
Definition: pandaNode.h:124
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool is_geom_node() const
A simple downcast check.
Definition: pandaNode.cxx:2068
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
get_x_size
Returns the width of the texture image in texels.
Definition: texture.h:334
This is the data for one array of a GeomVertexData structure.
get_on_texture
Returns the texture associated with the indicated stage, or NULL if no texture is associated.
Definition: textureAttrib.h:69
has_column
Returns true if the format has the named column, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.