2
0
Эх сурвалжийг харах

Revamping PStats to support texture memory, etc.

David Rose 24 жил өмнө
parent
commit
e50b9eb36c
64 өөрчлөгдсөн 1858 нэмэгдсэн , 679 устгасан
  1. 1 1
      panda/src/char/character.cxx
  2. 1 1
      panda/src/collide/collisionTraverser.cxx
  3. 0 1
      panda/src/collide/collisionTraverser.h
  4. 2 2
      panda/src/cull/cullTraverser.cxx
  5. 1 1
      panda/src/dgraph/dataGraphTraversal.cxx
  6. 68 13
      panda/src/display/graphicsStateGuardian.cxx
  7. 18 0
      panda/src/display/graphicsStateGuardian.h
  8. 6 6
      panda/src/display/graphicsWindow.cxx
  9. 77 0
      panda/src/display/textureContext.cxx
  10. 2 0
      panda/src/display/textureContext.h
  11. 54 40
      panda/src/glgsg/glGraphicsStateGuardian.cxx
  12. 7 1
      panda/src/pstatclient/Sources.pp
  13. 4 4
      panda/src/pstatclient/config_pstats.cxx
  14. 3 3
      panda/src/pstatclient/config_pstats.h
  15. 2 2
      panda/src/pstatclient/pStatClient.I
  16. 127 85
      panda/src/pstatclient/pStatClient.cxx
  17. 34 13
      panda/src/pstatclient/pStatClient.h
  18. 85 11
      panda/src/pstatclient/pStatCollector.I
  19. 39 11
      panda/src/pstatclient/pStatCollector.h
  20. 21 4
      panda/src/pstatclient/pStatCollectorDef.cxx
  21. 3 0
      panda/src/pstatclient/pStatCollectorDef.h
  22. 111 29
      panda/src/pstatclient/pStatFrameData.I
  23. 23 5
      panda/src/pstatclient/pStatFrameData.cxx
  24. 22 10
      panda/src/pstatclient/pStatFrameData.h
  25. 127 0
      panda/src/pstatclient/pStatProperties.cxx
  26. 21 0
      panda/src/pstatclient/pStatProperties.h
  27. 52 41
      panda/src/pstatclient/test_client.cxx
  28. 1 1
      panda/src/sgraphutil/directRenderTraverser.cxx
  29. 1 1
      pandatool/src/gtk-stats/gtkStatsGuide.cxx
  30. 1 1
      pandatool/src/gtk-stats/gtkStatsLabel.cxx
  31. 19 1
      pandatool/src/gtk-stats/gtkStatsMonitor.cxx
  32. 2 0
      pandatool/src/gtk-stats/gtkStatsMonitor.h
  33. 2 2
      pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx
  34. 10 9
      pandatool/src/gtk-stats/gtkStatsPianoWindow.cxx
  35. 1 1
      pandatool/src/gtk-stats/gtkStatsPianoWindow.h
  36. 11 2
      pandatool/src/gtk-stats/gtkStatsStripChart.cxx
  37. 1 1
      pandatool/src/gtk-stats/gtkStatsStripChart.h
  38. 185 48
      pandatool/src/gtk-stats/gtkStatsStripWindow.cxx
  39. 13 3
      pandatool/src/gtk-stats/gtkStatsStripWindow.h
  40. 5 12
      pandatool/src/gtk-stats/gtkStatsWindow.cxx
  41. 1 2
      pandatool/src/gtk-stats/gtkStatsWindow.h
  42. 58 16
      pandatool/src/pstatserver/pStatClientData.cxx
  43. 12 1
      pandatool/src/pstatserver/pStatClientData.h
  44. 26 2
      pandatool/src/pstatserver/pStatGraph.I
  45. 89 41
      pandatool/src/pstatserver/pStatGraph.cxx
  46. 16 8
      pandatool/src/pstatserver/pStatGraph.h
  47. 30 3
      pandatool/src/pstatserver/pStatMonitor.cxx
  48. 3 0
      pandatool/src/pstatserver/pStatMonitor.h
  49. 6 6
      pandatool/src/pstatserver/pStatPianoRoll.I
  50. 6 6
      pandatool/src/pstatserver/pStatPianoRoll.cxx
  51. 10 10
      pandatool/src/pstatserver/pStatPianoRoll.h
  52. 12 0
      pandatool/src/pstatserver/pStatReader.cxx
  53. 23 34
      pandatool/src/pstatserver/pStatStripChart.I
  54. 88 18
      pandatool/src/pstatserver/pStatStripChart.cxx
  55. 16 16
      pandatool/src/pstatserver/pStatStripChart.h
  56. 13 13
      pandatool/src/pstatserver/pStatThreadData.cxx
  57. 8 8
      pandatool/src/pstatserver/pStatThreadData.h
  58. 1 1
      pandatool/src/pstatserver/pStatView.I
  59. 242 99
      pandatool/src/pstatserver/pStatView.cxx
  60. 7 3
      pandatool/src/pstatserver/pStatView.h
  61. 7 7
      pandatool/src/pstatserver/pStatViewLevel.I
  62. 11 11
      pandatool/src/pstatserver/pStatViewLevel.cxx
  63. 8 6
      pandatool/src/pstatserver/pStatViewLevel.h
  64. 2 2
      pandatool/src/text-stats/textMonitor.cxx

+ 1 - 1
panda/src/char/character.cxx

@@ -20,7 +20,7 @@
 TypeHandle Character::_type_handle;
 
 #ifndef CPPPARSER
-PStatCollector Character::_anim_pcollector("App:Animation", RGBColorf(1,0,1), 30);
+PStatCollector Character::_anim_pcollector("App:Animation");
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/collide/collisionTraverser.cxx

@@ -19,7 +19,7 @@
 #include <pStatTimer.h>
 
 #ifndef CPPPARSER
-PStatCollector CollisionTraverser::_collisions_pcollector("App:Collisions", RGBColorf(1,0.5,0), 40);
+PStatCollector CollisionTraverser::_collisions_pcollector("App:Collisions");
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 0 - 1
panda/src/collide/collisionTraverser.h

@@ -17,7 +17,6 @@
 #include <pointerTo.h>
 #include <renderRelation.h>
 #include <pointerTo.h>
-
 #include <pStatCollector.h>
 
 #include <set>

+ 2 - 2
panda/src/cull/cullTraverser.cxx

@@ -29,8 +29,8 @@
 TypeHandle CullTraverser::_type_handle;
 
 #ifndef CPPPARSER
-PStatCollector CullTraverser::_cull_pcollector("Cull", RGBColorf(0,1,0), 10);
-PStatCollector CullTraverser::_draw_pcollector("Draw", RGBColorf(1,0,0), 20);
+PStatCollector CullTraverser::_cull_pcollector("Cull");
+PStatCollector CullTraverser::_draw_pcollector("Draw");
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/dgraph/dataGraphTraversal.cxx

@@ -9,7 +9,7 @@
 #include <pStatCollector.h>
 
 #ifndef CPPPARSER
-PStatCollector _dgraph_pcollector("App:Data Graph", RGBColorf(0.5,0.8,0.4), 30);
+PStatCollector _dgraph_pcollector("App:Data graph");
 #endif
   
 ////////////////////////////////////////////////////////////////////

+ 68 - 13
panda/src/display/graphicsStateGuardian.cxx

@@ -6,12 +6,21 @@
 #include "graphicsStateGuardian.h"
 #include "renderBuffer.h"
 #include "config_display.h"
+#include "textureContext.h"
 
 #include <clockObject.h>
 #include <geomNode.h>
 
 #include <algorithm>
 
+
+#ifndef CPPPARSER
+PStatCollector GraphicsStateGuardian::_total_texusage_pcollector("Texture usage");
+PStatCollector GraphicsStateGuardian::_active_texusage_pcollector("Texture usage:Active");
+PStatCollector GraphicsStateGuardian::_total_texmem_pcollector("Texture memory");
+PStatCollector GraphicsStateGuardian::_used_texmem_pcollector("Texture memory:In use");
+#endif
+
 TypeHandle GraphicsStateGuardian::_type_handle;
 TypeHandle GraphicsStateGuardian::GsgWindow::_type_handle;
 
@@ -420,7 +429,13 @@ end_decal(GeomNode *) {
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
 mark_prepared_texture(TextureContext *tc) {
-  return _prepared_textures.insert(tc).second;
+  bool prepared = _prepared_textures.insert(tc).second;
+#ifdef DO_PSTATS
+  if (prepared) {
+    _total_texusage_pcollector.add_level(tc->estimate_texture_memory() / 1048576.0);
+  }
+#endif
+  return prepared;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -428,22 +443,62 @@ mark_prepared_texture(TextureContext *tc) {
 //       Access: Protected
 //  Description: This is intended to be called from within
 //               release_texture().  It removes the indicated
-//               TextureContext pointer from the _prepared_textures set,
-//               and returns true if it was successfully removed
+//               TextureContext pointer from the _prepared_textures
+//               set, and returns true if it was successfully removed
 //               (i.e. it had been in the set).
 ////////////////////////////////////////////////////////////////////
 bool GraphicsStateGuardian::
 unmark_prepared_texture(TextureContext *tc) {
-  return (_prepared_textures.erase(tc) != 0);
+  bool removed = (_prepared_textures.erase(tc) != 0);
+#ifdef DO_PSTATS
+  if (removed) {
+    _total_texusage_pcollector.add_level(-(tc->estimate_texture_memory() / 1048576.0));
+  }
+#endif
+  return removed;
+}
+
+#ifdef DO_PSTATS
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::add_to_texture_record
+//       Access: Protected
+//  Description: Records that the indicated texture has been applied
+//               this frame, and thus must be present in current
+//               texture memory.  This function is only used to update
+//               the PStats current_texmem collector; it gets compiled
+//               out if we aren't using PStats.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+add_to_texture_record(TextureContext *tc) {
+  if (_current_textures.insert(tc).second) {
+    _active_texusage_pcollector.add_level(tc->estimate_texture_memory() / 1048576.0);
+  }
 }
 
-void GraphicsStateGuardian::traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg) {
-	for (Textures::const_iterator ti = _prepared_textures.begin(); ti != _prepared_textures.end();
-		 ++ti) {
-		bool bResult=(*pertex_callbackfn)(*ti,callback_arg);
-		if(!bResult)
-		   return;
-	} 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::clear_texture_record
+//       Access: Protected
+//  Description: Empties the texture record at the beginning of the
+//               frame, in preparation for calling
+//               add_to_texture_record() each time a texture is
+//               applied.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+clear_texture_record() {
+  _current_textures.clear();
+  _active_texusage_pcollector.set_level(0);
+}
+#endif  // DO_PSTATS
+
+
+void GraphicsStateGuardian::
+traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg) {
+  for (Textures::const_iterator ti = _prepared_textures.begin(); ti != _prepared_textures.end();
+       ++ti) {
+    bool bResult=(*pertex_callbackfn)(*ti,callback_arg);
+    if(!bResult)
+      return;
+  } 
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -461,8 +516,8 @@ get_factory() {
   return (*_factory);
 }
 
-void GraphicsStateGuardian::read_priorities(void)
-{
+void GraphicsStateGuardian::
+read_priorities(void) {
   GsgFactory &factory = get_factory();
   if (factory.get_num_preferred() == 0) {
     Config::ConfigTable::Symbol::iterator i;

+ 18 - 0
panda/src/display/graphicsStateGuardian.h

@@ -18,6 +18,7 @@
 #include <coordinateSystem.h>
 #include <factory.h>
 #include <renderTraverser.h>
+#include <pStatCollector.h>
 
 #include <list>
 
@@ -133,6 +134,17 @@ protected:
   bool mark_prepared_texture(TextureContext *tc);
   bool unmark_prepared_texture(TextureContext *tc);
 
+#ifdef DO_PSTATS
+  // These functions are used to update the current texture memory
+  // usage record in Pstats.
+  void add_to_texture_record(TextureContext *tc);
+  void clear_texture_record();
+  set<TextureContext *> _current_textures;
+#else
+  INLINE void add_to_texture_record(TextureContext *) { }
+  INLINE void clear_texture_record() { }
+#endif
+
 protected:
   int _buffer_mask;
   NodeAttributes _state;
@@ -158,6 +170,12 @@ protected:
 
   CoordinateSystem _coordinate_system;
 
+  // Statistics
+  static PStatCollector _total_texusage_pcollector;
+  static PStatCollector _active_texusage_pcollector;
+  static PStatCollector _total_texmem_pcollector;
+  static PStatCollector _used_texmem_pcollector;
+
 private:
   typedef set<TextureContext *> Textures;
   Textures _prepared_textures;  // NOTE: on win32 another DLL (e.g. libpandadx.dll) cannot access set directly due to exported template issue

+ 6 - 6
panda/src/display/graphicsWindow.cxx

@@ -25,12 +25,12 @@ TypeHandle GraphicsWindow::WindowPipe::_type_handle;
 GraphicsWindow::WindowFactory *GraphicsWindow::_factory = NULL;
 
 #ifndef CPPPARSER
-PStatCollector GraphicsWindow::_app_pcollector("App", RGBColorf(0,1,1));
-PStatCollector GraphicsWindow::_show_code_pcollector("App:Show Code", RGBColorf(0.8,0.2,1));
-PStatCollector GraphicsWindow::_swap_pcollector("Draw:Swap Buffers", RGBColorf(0.5,1,0.8));
-PStatCollector GraphicsWindow::_clear_pcollector("Draw:Clear", RGBColorf(0.5,0.7,0.7));
-PStatCollector GraphicsWindow::_show_fps_pcollector("Draw:Show fps", RGBColorf(0.5,0.8,1));
-PStatCollector GraphicsWindow::_make_current_pcollector("Draw:Make Current", RGBColorf(1,0.6,0.3));
+PStatCollector GraphicsWindow::_app_pcollector("App");
+PStatCollector GraphicsWindow::_show_code_pcollector("App:Show code");
+PStatCollector GraphicsWindow::_swap_pcollector("Draw:Swap buffers");
+PStatCollector GraphicsWindow::_clear_pcollector("Draw:Clear");
+PStatCollector GraphicsWindow::_show_fps_pcollector("Draw:Show fps");
+PStatCollector GraphicsWindow::_make_current_pcollector("Draw:Make current");
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 77 - 0
panda/src/display/textureContext.cxx

@@ -5,4 +5,81 @@
 
 #include "textureContext.h"
 
+#include <texture.h>
+#include <pixelBuffer.h>
+
 TypeHandle TextureContext::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureContext::estimate_texture_memory
+//       Access: Public, Virtual
+//  Description: Estimates the amount of texture memory that will be
+//               consumed by loading this texture.  This is mainly
+//               useful for debugging and reporting purposes.
+//
+//               Returns a value in bytes.
+////////////////////////////////////////////////////////////////////
+size_t TextureContext::
+estimate_texture_memory() {
+  PixelBuffer *pb = _texture->_pbuffer;
+
+  size_t pixels = pb->get_xsize() * pb->get_ysize();
+
+  size_t bpp = 1;
+  switch (pb->get_format()) {
+  case PixelBuffer::F_rgb332:
+  case PixelBuffer::F_alpha:
+  case PixelBuffer::F_red:
+  case PixelBuffer::F_green:
+  case PixelBuffer::F_blue:
+  case PixelBuffer::F_luminance:
+  case PixelBuffer::F_luminance_alpha:
+  case PixelBuffer::F_luminance_alphamask:
+  case PixelBuffer::F_color_index:
+  case PixelBuffer::F_stencil_index:
+  case PixelBuffer::F_depth_component:
+    bpp = 1;
+    break;
+
+  case PixelBuffer::F_rgba:
+  case PixelBuffer::F_rgba4:
+  case PixelBuffer::F_rgbm:
+  case PixelBuffer::F_rgb:
+  case PixelBuffer::F_rgb5:
+  case PixelBuffer::F_rgba5:
+    bpp = 2;
+    break;
+
+  case PixelBuffer::F_rgb8:
+  case PixelBuffer::F_rgba8:
+    bpp = 4;
+    break;
+
+  case PixelBuffer::F_rgba12:
+  case PixelBuffer::F_rgb12:
+    bpp = 6;
+    break;
+  }
+
+  size_t bytes = pixels * bpp;
+
+  bool use_mipmaps;
+  switch (_texture->get_minfilter()) {
+  case Texture::FT_nearest_mipmap_nearest:
+  case Texture::FT_linear_mipmap_nearest:
+  case Texture::FT_nearest_mipmap_linear:
+  case Texture::FT_linear_mipmap_linear:
+    use_mipmaps = true;
+    break;
+    
+  default:
+    use_mipmaps = false;
+    break;
+  }
+
+  if (use_mipmaps) {
+    bytes += bytes / 3;
+  }
+
+  return bytes;
+}

+ 2 - 0
panda/src/display/textureContext.h

@@ -29,6 +29,8 @@ class EXPCL_PANDA TextureContext : public TypedObject {
 public:
   INLINE TextureContext(Texture *tex);
 
+  virtual size_t estimate_texture_memory();
+
   // This cannot be a PT(Texture), because the texture and the GSG
   // both own their TextureContexts!  That would create a circular
   // reference count.

+ 54 - 40
panda/src/glgsg/glGraphicsStateGuardian.cxx

@@ -115,7 +115,7 @@ issue_texcoord_gl(const Geom *geom, Geom::TexCoordIterator &tciterator) {
 
 static void
 issue_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
-           const GraphicsStateGuardianBase *) {
+               const GraphicsStateGuardianBase *) {
   const Colorf &color = geom->get_next_color(citerator);
   //  glgsg_cat.debug() << "Issuing color " << color << "\n";
   glColor4fv(color.get_data());
@@ -123,7 +123,7 @@ issue_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
 
 static void
 issue_transformed_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
-               const GraphicsStateGuardianBase *gsg) {
+                           const GraphicsStateGuardianBase *gsg) {
   const GLGraphicsStateGuardian *glgsg = DCAST(GLGraphicsStateGuardian, gsg);
   const Colorf &color = geom->get_next_color(citerator);
 
@@ -468,11 +468,24 @@ render_frame(const AllAttributesWrapper &initial_state) {
     << " --------------------------------------------" << endl;
 #endif
 
-  _lighting_enabled_this_frame = false;
-
   _win->begin_frame();
   report_errors();
   _decal_level = 0;
+  _lighting_enabled_this_frame = false;
+
+#ifdef DO_PSTATS
+  // For Pstats to track our current texture memory usage, we have to
+  // reset the set of current textures each frame.
+  clear_texture_record();
+
+  // But since we don't get sent a new issue_texture() unless our
+  // texture state has changed, we have to be sure to clear the
+  // current texture state now.  A bit unfortunate, but probably not
+  // measurably expensive.
+  NodeAttributes state;
+  state.set_attribute(TextureTransition::get_class_type(), new TextureAttribute);
+  set_state(state, false);
+#endif
 
   if (_clear_buffer_type != 0) {
     // First, clear the entire window.
@@ -723,10 +736,10 @@ draw_point(const GeomPoint *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1224,10 +1237,10 @@ draw_polygon(const GeomPolygon *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
@@ -1295,10 +1308,10 @@ draw_tri(const GeomTri *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
@@ -1362,10 +1375,10 @@ draw_quad(const GeomQuad *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
@@ -1430,10 +1443,10 @@ draw_tristrip(const GeomTristrip *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
@@ -1519,10 +1532,10 @@ draw_trifan(const GeomTrifan *geom) {
   }
 
   GeomIssuer issuer(geom, this,
-            issue_vertex_gl,
-            issue_normal_gl,
-            issue_texcoord_gl,
-            issue_color);
+                    issue_vertex_gl,
+                    issue_normal_gl,
+                    issue_texcoord_gl,
+                    issue_color);
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
@@ -1700,6 +1713,7 @@ prepare_texture(Texture *tex) {
 void GLGraphicsStateGuardian::
 apply_texture(TextureContext *tc) {
   //  activate();
+  add_to_texture_record(tc);
   bind_texture(tc);
 
   /*
@@ -1754,7 +1768,7 @@ static int binary_log_cap(const int x) {
 //     Function: GLGraphicsStateGuardian::copy_texture
 //       Access: Public, Virtual
 //  Description: Copy the pixel region indicated by the display
-//       region from the framebuffer into texture memory
+//               region from the framebuffer into texture memory
 ////////////////////////////////////////////////////////////////////
 void GLGraphicsStateGuardian::
 copy_texture(TextureContext *tc, const DisplayRegion *dr) {
@@ -3496,20 +3510,20 @@ bind_texture(TextureContext *tc) {
 void GLGraphicsStateGuardian::
 specify_texture(Texture *tex) {
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 
-          get_texture_wrap_mode(tex->get_wrapu()));
+                  get_texture_wrap_mode(tex->get_wrapu()));
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
-          get_texture_wrap_mode(tex->get_wrapv()));
+                  get_texture_wrap_mode(tex->get_wrapv()));
 
   if (gl_force_mipmaps) {
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
-            GL_LINEAR_MIPMAP_LINEAR);
+                    GL_LINEAR_MIPMAP_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
   } else {
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
-            get_texture_filter_type(tex->get_minfilter()));
+                    get_texture_filter_type(tex->get_minfilter()));
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
-            get_texture_filter_type(tex->get_magfilter()));
+                    get_texture_filter_type(tex->get_magfilter()));
   }
   report_errors();
 }
@@ -3554,20 +3568,20 @@ apply_texture_immediate(Texture *tex) {
     if (use_mipmaps || gl_force_mipmaps) {
 #ifndef NDEBUG
       if (gl_show_mipmaps) {
-    build_phony_mipmaps(tex);
-    return;
+        build_phony_mipmaps(tex);
+        return;
       }
 #endif
       gluBuild2DMipmaps(GL_TEXTURE_2D, internal_format,
-            pb->get_xsize(), pb->get_ysize(),
-            external_format, type, pb->_image);
+                        pb->get_xsize(), pb->get_ysize(),
+                        external_format, type, pb->_image);
       return;
     }
   }
 
   glTexImage2D( GL_TEXTURE_2D, 0, internal_format,
-        pb->get_xsize(), pb->get_ysize(), pb->get_border(),
-        external_format, type, pb->_image );
+                pb->get_xsize(), pb->get_ysize(), pb->get_border(),
+                external_format, type, pb->_image );
   report_errors();
 }
 

+ 7 - 1
panda/src/pstatclient/Sources.pp

@@ -14,7 +14,8 @@
     pStatCollector.I pStatCollector.h \
     pStatCollectorDef.cxx \
     pStatCollectorDef.h pStatFrameData.I pStatFrameData.cxx \
-    pStatFrameData.h pStatServerControlMessage.cxx \
+    pStatFrameData.h pStatProperties.cxx pStatProperties.h \
+    pStatServerControlMessage.cxx \
     pStatServerControlMessage.h \
     pStatThread.I pStatThread.h \
     pStatTimer.I pStatTimer.h
@@ -31,6 +32,11 @@
 #end lib_target
 
 #begin test_bin_target
+  #define LOCAL_LIBS \
+    pstatclient
+  #define OTHER_LIBS \
+    pystub
+
   #define TARGET test_client
 
   #define SOURCES \

+ 4 - 4
panda/src/pstatclient/config_pstats.cxx

@@ -18,18 +18,18 @@ string get_pstats_name() {
   return config_pstats.GetString("pstats-name", "Panda Stats");
 }
 
-double get_pstats_max_rate() {
-  return config_pstats.GetDouble("pstats-max-rate", 30.0);
+float get_pstats_max_rate() {
+  return config_pstats.GetFloat("pstats-max-rate", 30.0);
 }
 
 const string pstats_host = config_pstats.GetString("pstats-host", "localhost");
 const int pstats_port = config_pstats.GetInt("pstats-port", 5180);
-const double pstats_target_frame_rate = config_pstats.GetDouble("pstats-target-frame-rate", 30.0);
+const float pstats_target_frame_rate = config_pstats.GetFloat("pstats-target-frame-rate", 30.0);
 
 // The rest are different in that they directly control the server,
 // not the client.
 const bool pstats_scroll_mode = config_pstats.GetBool("pstats-scroll-mode", true);
-const double pstats_history = config_pstats.GetDouble("pstats-history", 30.0);
+const float pstats_history = config_pstats.GetFloat("pstats-history", 30.0);
 
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libpstatclient

+ 3 - 3
panda/src/pstatclient/config_pstats.h

@@ -15,13 +15,13 @@
 NotifyCategoryDecl(pstats, EXPCL_PANDA, EXPTP_PANDA);
 
 extern EXPCL_PANDA string get_pstats_name();
-extern EXPCL_PANDA double get_pstats_max_rate();
+extern EXPCL_PANDA float get_pstats_max_rate();
 extern EXPCL_PANDA const string pstats_host; 
 extern EXPCL_PANDA const int pstats_port;
-extern EXPCL_PANDA const double pstats_target_frame_rate;
+extern EXPCL_PANDA const float pstats_target_frame_rate;
 
 extern EXPCL_PANDA const bool pstats_scroll_mode;
-extern EXPCL_PANDA const double pstats_history;
+extern EXPCL_PANDA const float pstats_history;
 
 extern EXPCL_PANDA void init_libpstatclient();
 

+ 2 - 2
panda/src/pstatclient/pStatClient.I

@@ -42,7 +42,7 @@ get_client_name() const {
 //               thread.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatClient::
-set_max_rate(double rate) {
+set_max_rate(float rate) {
   _max_rate = rate;
 }
 
@@ -53,7 +53,7 @@ set_max_rate(double rate) {
 //               sent to the server per second, per thread.  See
 //               set_max_rate().
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatClient::
+INLINE float PStatClient::
 get_max_rate() const {
   return _max_rate;
 }

+ 127 - 85
panda/src/pstatclient/pStatClient.cxx

@@ -14,6 +14,7 @@
 #include "pStatCollector.h"
 #include "pStatThread.h"
 #include "config_pstats.h"
+#include "pStatProperties.h"
 
 #include <algorithm>
 
@@ -25,6 +26,18 @@
 
 PStatClient *PStatClient::_global_pstats = NULL;
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClient::PerThreadData::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PStatClient::PerThreadData::
+PerThreadData() {
+  _has_level = false;
+  _level = 0.0;
+  _nested_count = 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatClient::Constructor
 //       Access: Public
@@ -196,36 +209,55 @@ get_main_thread() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatClient::make_collector
+//     Function: PStatClient::make_collector_with_relname
 //       Access: Private
 //  Description: Returns a PStatCollector suitable for measuring
 //               categories with the indicated name.  This is normally
 //               called by a PStatCollector constructor.
+//
+//               The name may contain colons; if it does, it specifies
+//               a relative path to the client indicated by the parent
+//               index.
 ////////////////////////////////////////////////////////////////////
 PStatCollector PStatClient::
-make_collector(int parent_index, string fullname) {
-  if (fullname.empty()) {
-    fullname = "Unnamed";
+make_collector_with_relname(int parent_index, string relname) {
+  if (relname.empty()) {
+    relname = "Unnamed";
   }
 
   // Skip any colons at the beginning of the name.
   size_t start = 0;
-  while (start < fullname.size() && fullname[start] == ':') {
+  while (start < relname.size() && relname[start] == ':') {
     start++;
   }
   
   // If the name contains a colon (after the initial colon), it means
   // we are making a nested collector.
-  size_t colon = fullname.find(':', start);
-  if (colon != string::npos) {
-    string parent_name = fullname.substr(start, colon - start);
+  size_t colon = relname.find(':', start);
+  while (colon != string::npos) {
+    string parent_name = relname.substr(start, colon - start);
     PStatCollector parent_collector = 
-      make_collector(parent_index, parent_name);
-    return make_collector(parent_collector._index, fullname.substr(colon + 1));
+      make_collector_with_name(parent_index, parent_name);
+    parent_index = parent_collector._index;
+    relname = relname.substr(colon + 1);
+    colon = relname.find(':', start);
   }
 
-  string name = fullname.substr(start);
+  string name = relname.substr(start);
+  return make_collector_with_name(parent_index, name);
+}
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClient::make_collector_with_name
+//       Access: Private
+//  Description: Returns a PStatCollector suitable for measuring
+//               categories with the indicated name.  This is normally
+//               called by a PStatCollector constructor.
+//
+//               The name should not contain colons.
+////////////////////////////////////////////////////////////////////
+PStatCollector PStatClient::
+make_collector_with_name(int parent_index, const string &name) {
   nassertr(parent_index >= 0 && parent_index < (int)_collectors.size(),
 	   PStatCollector());
 
@@ -251,51 +283,20 @@ make_collector(int parent_index, string fullname) {
   int new_index = _collectors.size();
   parent._children.insert(ThingsByName::value_type(name, new_index));
 
-  Collector collector;
+  // Extending the vector invalidates the parent reference, above.
+  _collectors.push_back(Collector());
+  Collector &collector = _collectors.back();
   collector._def = new PStatCollectorDef(new_index, name);
-  collector._def->_parent_index = parent_index;
 
-  // We need one nested_count for each thread.
-  while (collector._nested_count.size() < _threads.size()) {
-    collector._nested_count.push_back(0);
-  }
+  collector._def->set_parent(*_collectors[parent_index]._def);
+  initialize_collector_def(this, collector._def);
 
-  _collectors.push_back(collector);
-
-  return PStatCollector(this, new_index);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PStatClient::make_collector
-//       Access: Private
-//  Description: This flavor of make_collector will make a new
-//               collector and automatically set up some of its
-//               properties.
-////////////////////////////////////////////////////////////////////
-PStatCollector PStatClient::
-make_collector(int parent_index, const string &fullname,
-	       const RGBColorf &suggested_color, int sort) {
-  PStatCollector c = make_collector(parent_index, fullname);
-  nassertr(c._client == this, PStatCollector());
-  nassertr(c._index >= 0 && c._index < (int)_collectors.size(), PStatCollector());
-
-  PStatCollectorDef *def = _collectors[c._index]._def;
-  nassertr(def != (PStatCollectorDef *)NULL, PStatCollector());
-
-  if (suggested_color != RGBColorf::zero() &&
-      def->_suggested_color != suggested_color) {
-    // We need to change the suggested color.
-    def->_suggested_color = suggested_color;
-    _collectors_reported = min(_collectors_reported, c._index);
+  // We need one PerThreadData for each thread.
+  while (collector._per_thread.size() < _threads.size()) {
+    collector._per_thread.push_back(PerThreadData());
   }
 
-  if (sort != -1 && def->_sort != sort) {
-    // We need to change the sort.
-    def->_sort = sort;
-    _collectors_reported = min(_collectors_reported, c._index);
-  }
-
-  return c;
+  return PStatCollector(this, new_index);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -329,12 +330,12 @@ make_thread(const string &name) {
 
   _threads.push_back(thread);
 
-  // We need an additional nested_count for this thread in all of the
+  // We need an additional PerThreadData for this thread in all of the
   // collectors.
   Collectors::iterator ci;
   for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) {
-    (*ci)._nested_count.push_back(0);
-    nassertr((*ci)._nested_count.size() == _threads.size(), PStatThread());
+    (*ci)._per_thread.push_back(PerThreadData());
+    nassertr((*ci)._per_thread.size() == _threads.size(), PStatThread());
   }
 
   return PStatThread(this, new_index);
@@ -440,11 +441,11 @@ ns_disconnect() {
   
   Collectors::iterator ci;
   for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) {
-    vector_int::iterator ii;
-    for (ii = (*ci)._nested_count.begin(); 
-	   ii != (*ci)._nested_count.end(); 
+    PerThread::iterator ii;
+    for (ii = (*ci)._per_thread.begin(); 
+         ii != (*ci)._per_thread.end(); 
 	 ++ii) {
-      (*ii) = 0;
+      (*ii)._nested_count = 0;
     }
   }
 }
@@ -464,20 +465,20 @@ ns_is_connected() const {
 //       Access: Private
 //  Description: Marks the indicated collector index as started.
 //               Normally you would not use this interface directly;
-//               instead, call pStatCollector::start().
+//               instead, call PStatCollector::start().
 ////////////////////////////////////////////////////////////////////
 void PStatClient::
-start(int collector_index, int thread_index, double as_of) {
+start(int collector_index, int thread_index, float as_of) {
   nassertv(collector_index >= 0 && collector_index < (int)_collectors.size());
   nassertv(thread_index >= 0 && thread_index < (int)_threads.size());
 
   if (_threads[thread_index]._is_active) {
-    if (_collectors[collector_index]._nested_count[thread_index] == 0) {
+    if (_collectors[collector_index]._per_thread[thread_index]._nested_count == 0) {
       // This collector wasn't already started in this thread; record
       // a new data point.
       _threads[thread_index]._frame_data.add_start(collector_index, as_of);
     }
-    _collectors[collector_index]._nested_count[thread_index]++;
+    _collectors[collector_index]._per_thread[thread_index]._nested_count++;
   }
 }
 
@@ -486,15 +487,15 @@ start(int collector_index, int thread_index, double as_of) {
 //       Access: Private
 //  Description: Marks the indicated collector index as stopped.
 //               Normally you would not use this interface directly;
-//               instead, call pStatCollector::stop().
+//               instead, call PStatCollector::stop().
 ////////////////////////////////////////////////////////////////////
 void PStatClient::
-stop(int collector_index, int thread_index, double as_of) {
+stop(int collector_index, int thread_index, float as_of) {
   nassertv(collector_index >= 0 && collector_index < (int)_collectors.size());
   nassertv(thread_index >= 0 && thread_index < (int)_threads.size());
 
   if (_threads[thread_index]._is_active) {
-    if (_collectors[collector_index]._nested_count[thread_index] == 0) {
+    if (_collectors[collector_index]._per_thread[thread_index]._nested_count == 0) {
       pstats_cat.warning()
 	<< "Collector " << get_collector_fullname(collector_index)
 	<< " was already stopped in thread " << get_thread_name(thread_index)
@@ -502,9 +503,9 @@ stop(int collector_index, int thread_index, double as_of) {
       return;
     }
     
-    _collectors[collector_index]._nested_count[thread_index]--;
+    _collectors[collector_index]._per_thread[thread_index]._nested_count--;
     
-    if (_collectors[collector_index]._nested_count[thread_index] == 0) {
+    if (_collectors[collector_index]._per_thread[thread_index]._nested_count == 0) {
       // This collector has now been completely stopped; record a new
       // data point.
       _threads[thread_index]._frame_data.add_stop(collector_index, as_of);
@@ -512,6 +513,54 @@ stop(int collector_index, int thread_index, double as_of) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClient::clear_level
+//       Access: Private
+//  Description: Removes the level value from the indicated collector.
+//               The collector will no longer be reported as having
+//               any particular level value.
+//
+//               Normally you would not use this interface directly;
+//               instead, call PStatCollector::clear_level().
+////////////////////////////////////////////////////////////////////
+void PStatClient::
+clear_level(int collector_index, int thread_index) {
+  _collectors[collector_index]._per_thread[thread_index]._has_level = false;
+  _collectors[collector_index]._per_thread[thread_index]._level = 0.0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClient::set_level
+//       Access: Private
+//  Description: Sets the level value for the indicated collector to
+//               the given amount.
+//
+//               Normally you would not use this interface directly;
+//               instead, call PStatCollector::set_level().
+////////////////////////////////////////////////////////////////////
+void PStatClient::
+set_level(int collector_index, int thread_index, float level) {
+  _collectors[collector_index]._per_thread[thread_index]._has_level = true;
+  _collectors[collector_index]._per_thread[thread_index]._level = level;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClient::add_level
+//       Access: Private
+//  Description: Adds the given value (which may be negative) to the
+//               current value for the given collector.  If the
+//               collector does not already have a level value, it is
+//               initialized to 0.
+//
+//               Normally you would not use this interface directly;
+//               instead, call PStatCollector::add_level().
+////////////////////////////////////////////////////////////////////
+void PStatClient::
+add_level(int collector_index, int thread_index, float increment) {
+  _collectors[collector_index]._per_thread[thread_index]._has_level = true;
+  _collectors[collector_index]._per_thread[thread_index]._level += increment;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatClient::new_frame
 //       Access: Private
@@ -542,28 +591,21 @@ new_frame(int thread_index) {
     return;
   }
 
-  double frame_start = _clock.get_real_time();
+  float frame_start = _clock.get_real_time();
 
   if (!thread._frame_data.is_empty()) {
     // Collector 0 is the whole frame.
     stop(0, thread_index, frame_start);
 
-    /*
-      We don't need to do this, since the stats server will do it.
-      Why should we waste our time?
-
-    // Make sure all of our Collectors have turned themselves off now
-    // at the end of the frame.
-    Collectors::iterator ci;
-    for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) {
-      if ((*ci)._nested_count > 0) {
-	pstats_cat.warning()
-	  << "Collector " << get_collector_fullname((*ci)._def->_index)
-	  << " wasn't stopped!\n";
-	(*ci)._nested_count = 0;
+    // Fill up the level data for all the collectors who have level
+    // data for this thread.
+    int num_collectors = _collectors.size();
+    for (int i = 0; i < num_collectors; i++) {
+      const PerThreadData &ptd = _collectors[i]._per_thread[thread_index];
+      if (ptd._has_level) {
+        thread._frame_data.add_level(i, ptd._level);
       }
-    } */
-
+    }
     transmit_frame_data(thread_index);
   }
 
@@ -586,8 +628,8 @@ transmit_frame_data(int thread_index) {
     // We don't want to send too many packets in a hurry and flood the
     // server.  In fact, we don't want to send more than
     // _max_rate packets per second, per thread.
-    double min_packet_delay = 1.0 / _max_rate;
-    double now = _clock.get_real_time();
+    float min_packet_delay = 1.0 / _max_rate;
+    float now = _clock.get_real_time();
 
     if (now - _threads[thread_index]._last_packet > min_packet_delay) {
       nassertv(_got_udp_port);

+ 34 - 13
panda/src/pstatclient/pStatClient.h

@@ -11,10 +11,10 @@
 #include "pStatFrameData.h"
 
 #include <clockObject.h>
-#include <vector_int.h>
 #include <luse.h>
+#include <map>
 
-#ifdef DO_PSTATS
+#ifdef HAVE_NET
 #include <connectionManager.h>
 #include <queuedConnectionReader.h>
 #include <connectionWriter.h>
@@ -43,8 +43,8 @@ public:
 
   INLINE void set_client_name(const string &name);
   INLINE string get_client_name() const;
-  INLINE void set_max_rate(double rate);
-  INLINE double get_max_rate() const;
+  INLINE void set_max_rate(float rate);
+  INLINE float get_max_rate() const;
 
   int get_num_collectors() const;
   PStatCollector get_collector(int index) const;
@@ -72,13 +72,16 @@ private:
   void ns_disconnect();
   bool ns_is_connected() const;
 
-  PStatCollector make_collector(int parent_index, string fullname);
-  PStatCollector make_collector(int parent_index, const string &fullname,
-				const RGBColorf &suggested_color, int sort);
+  PStatCollector make_collector_with_relname(int parent_index, string relname);
+  PStatCollector make_collector_with_name(int parent_index, const string &name);
   PStatThread make_thread(const string &name);
 
-  void start(int collector_index, int thread_index, double as_of);
-  void stop(int collector_index, int thread_index, double as_of);
+  void start(int collector_index, int thread_index, float as_of);
+  void stop(int collector_index, int thread_index, float as_of);
+
+  void clear_level(int collector_index, int thread_index);
+  void set_level(int collector_index, int thread_index, float level);
+  void add_level(int collector_index, int thread_index, float increment);
 
   void new_frame(int thread_index);
   void transmit_frame_data(int thread_index);
@@ -91,27 +94,45 @@ private:
   typedef map<string, int> ThingsByName;
   ThingsByName _threads_by_name;
 
+  // This is for the data that is per-collector, per-thread.  A vector
+  // of these is stored in each Collector object, below, indexed by
+  // thread index.
+  class PerThreadData {
+  public:
+    PerThreadData();
+    bool _has_level;
+    float _level;
+    int _nested_count;
+  };
+  typedef vector<PerThreadData> PerThread;
+
+  // This is where the meat of the Collector data is stored.  (All the
+  // stuff in PStatCollector and PStatCollectorDef is just fluff.)
   class Collector {
   public:
     PStatCollectorDef *_def;
-    vector_int _nested_count;
     ThingsByName _children;
+
+    PerThread _per_thread;
   };
   typedef vector<Collector> Collectors;
   Collectors _collectors;
 
+  // This defines a single thread, i.e. a separate chain of execution,
+  // independent of all other threads.  Timing and level data are
+  // maintained separately for each thread.
   class Thread {
   public:
     string _name;
     PStatFrameData _frame_data;
     bool _is_active;
     int _frame_number;
-    double _last_packet;
+    float _last_packet;
   };
-
   typedef vector<Thread> Threads;
   Threads _threads;
 
+
 private:
   // Networking stuff
   string get_hostname();
@@ -137,7 +158,7 @@ private:
 
   string _hostname;
   string _client_name;
-  double _max_rate;
+  float _max_rate;
 
   static PStatClient *_global_pstats;
   friend class PStatCollector;

+ 85 - 11
panda/src/pstatclient/pStatCollector.I

@@ -52,13 +52,11 @@ PStatCollector(PStatClient *client, int index) :
 //               otherwise, the global client is used.
 ////////////////////////////////////////////////////////////////////
 INLINE PStatCollector::
-PStatCollector(const string &name, const RGBColorf &suggested_color,
-	       int sort, PStatClient *client) {
+PStatCollector(const string &name, PStatClient *client) {
   if (client == (PStatClient *)NULL) {
     client = PStatClient::get_global_pstats();
   }
-  PStatCollector collector(client->make_collector(0, name, suggested_color, sort));
-  (*this) = collector;
+  (*this) = client->make_collector_with_relname(0, name);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -85,12 +83,10 @@ PStatCollector(const string &name, const RGBColorf &suggested_color,
 //               as its parent.
 ////////////////////////////////////////////////////////////////////
 INLINE PStatCollector::
-PStatCollector(const PStatCollector &parent, const string &name, 
-	       const RGBColorf &suggested_color, int sort) {
+PStatCollector(const PStatCollector &parent, const string &name) {
   nassertv(parent._client != (PStatClient *)NULL);
-  PStatCollector collector(parent._client->make_collector
-			   (parent._index, name, suggested_color, sort));
-  (*this) = collector;
+  (*this) = 
+    parent._client->make_collector_with_relname(parent._index, name);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -147,7 +143,7 @@ start(const PStatThread &thread) {
 //               a monotonically increasing series of time values.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatCollector::
-start(const PStatThread &thread, double as_of) {
+start(const PStatThread &thread, float as_of) {
   _client->start(_index, thread._index, as_of);
 }
 
@@ -182,8 +178,86 @@ stop(const PStatThread &thread) {
 //               a monotonically increasing series of time values.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatCollector::
-stop(const PStatThread &thread, double as_of) {
+stop(const PStatThread &thread, float as_of) {
   _client->stop(_index, thread._index, as_of);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::clear_level
+//       Access: Public
+//  Description: Removes the level setting associated with this
+//               collector for the main thread.  The collector
+//               will no longer show up on any level graphs in the
+//               main thread.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+clear_level() {
+  _client->clear_level(_index, 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::clear_level
+//       Access: Public
+//  Description: Removes the level setting associated with this
+//               collector for the indicated thread.  The collector
+//               will no longer show up on any level graphs in this
+//               thread.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+clear_level(const PStatThread &thread) {
+  _client->clear_level(_index, thread._index);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::set_level
+//       Access: Public
+//  Description: Sets the level setting associated with this
+//               collector for the main thread to the indicated
+//               value.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+set_level(float level) {
+  _client->set_level(_index, 0, level);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::set_level
+//       Access: Public
+//  Description: Sets the level setting associated with this
+//               collector for the indicated thread to the indicated
+//               value.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+set_level(const PStatThread &thread, float level) {
+  _client->set_level(_index, thread._index, level);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::add_level
+//       Access: Public
+//  Description: Adds the indicated increment (which may be negative)
+//               to the level setting associated with this collector
+//               for the main thread.  If the collector did not
+//               already have a level setting for the main thread, it
+//               is initialized to 0.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+add_level(float increment) {
+  _client->add_level(_index, 0, increment);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollector::add_level
+//       Access: Public
+//  Description: Adds the indicated increment (which may be negative)
+//               to the level setting associated with this collector
+//               for the indicated thread.  If the collector did not
+//               already have a level setting for this thread, it is
+//               initialized to 0.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatCollector::
+add_level(const PStatThread &thread, float increment) {
+  _client->add_level(_index, thread._index, increment);
+}
+
 #endif  // DO_PSTATS

+ 39 - 11
panda/src/pstatclient/pStatCollector.h

@@ -16,8 +16,26 @@
 ////////////////////////////////////////////////////////////////////
 // 	 Class : PStatCollector
 // Description : A lightweight class that represents a single element
-//               that may be timed via stats.  Bracket the code to be
-//               timed with calls to start() and stop().
+//               that may be timed and/or counted via stats.
+//
+//               Collectors can be used to measure two different kinds
+//               of values: elapsed time, and "other".  
+//
+//               To measure elapsed time, call start() and stop() as
+//               appropriate to bracket the section of code you want
+//               to time (or use a PStatTimer to do this
+//               automatically).
+//
+//               To measure anything else, call set_level() and/or
+//               add_level() to set the "level" value associated with
+//               this collector.  The meaning of the value set for the
+//               "level" is entirely up to the user; it may represent
+//               the number of triangles rendered or the kilobytes of
+//               texture memory consumed, for instance.  The level set
+//               will remain fixed across multiple frames until it is
+//               reset via another set_level() or adjusted via a call
+//               to add_level().  It may also be completely removed
+//               via clear_level().
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA PStatCollector {
 #ifdef DO_PSTATS
@@ -28,24 +46,27 @@ private:
 
 public:
   INLINE PStatCollector(const string &name, 
-			const RGBColorf &suggested_color = RGBColorf::zero(),
-			int sort = -1,
 			PStatClient *client = NULL);
   INLINE PStatCollector(const PStatCollector &parent,
-			const string &name,
-			const RGBColorf &suggested_color = RGBColorf::zero(),
-			int sort = -1);
+			const string &name);
 
   INLINE PStatCollector(const PStatCollector &copy);
   INLINE void operator = (const PStatCollector &copy);
 
   INLINE void start();
   INLINE void start(const PStatThread &thread);
-  INLINE void start(const PStatThread &thread, double as_of);
+  INLINE void start(const PStatThread &thread, float as_of);
 
   INLINE void stop();
   INLINE void stop(const PStatThread &thread);
-  INLINE void stop(const PStatThread &thread, double as_of);
+  INLINE void stop(const PStatThread &thread, float as_of);
+
+  INLINE void clear_level();
+  INLINE void clear_level(const PStatThread &thread);
+  INLINE void set_level(float level);
+  INLINE void set_level(const PStatThread &thread, float level);
+  INLINE void add_level(float increment);
+  INLINE void add_level(const PStatThread &thread, float increment);
 
 private:
   PStatClient *_client;
@@ -66,11 +87,18 @@ public:
 
   INLINE void start() { }
   INLINE void start(const PStatThread &) { }
-  INLINE void start(const PStatThread &, double) { }
+  INLINE void start(const PStatThread &, float) { }
 
   INLINE void stop() { }
   INLINE void stop(const PStatThread &) { }
-  INLINE void stop(const PStatThread &, double) { }
+  INLINE void stop(const PStatThread &, float) { }
+
+  INLINE void clear_level() { }
+  INLINE void clear_level(const PStatThread &) { }
+  INLINE void set_level(float) { }
+  INLINE void set_level(const PStatThread &, float) { }
+  INLINE void add_level(float) { }
+  INLINE void add_level(const PStatThread &, float) { }
 
 #endif  // DO_PSTATS
 };

+ 21 - 4
panda/src/pstatclient/pStatCollectorDef.cxx

@@ -20,6 +20,7 @@ PStatCollectorDef() {
   _parent_index = 0;
   _suggested_color.set(0.0, 0.0, 0.0);
   _sort = -1;
+  _suggested_scale = 0.0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -35,8 +36,23 @@ PStatCollectorDef(int index, const string &name) :
   _parent_index = 0;
   _suggested_color.set(0.0, 0.0, 0.0);
   _sort = -1;
+  _suggested_scale = 0.0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatCollectorDef::set_parent
+//       Access: Public
+//  Description: This is normally called only by the PStatClient when
+//               the new PStatCollectorDef is created; it sets the
+//               parent of the CollectorDef and inherits whatever
+//               properties are appropriate.
+////////////////////////////////////////////////////////////////////
+void PStatCollectorDef::
+set_parent(const PStatCollectorDef &parent) {
+  _parent_index = parent._index;
+  _level_units = parent._level_units;
+  _suggested_scale = parent._suggested_scale;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatCollectorDef::write_datagram
@@ -51,6 +67,8 @@ write_datagram(Datagram &destination) const {
   destination.add_int16(_parent_index);
   _suggested_color.write_datagram(destination);
   destination.add_int16(_sort);
+  destination.add_string(_level_units);
+  destination.add_float32(_suggested_scale);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -64,8 +82,7 @@ read_datagram(DatagramIterator &source) {
   _name = source.get_string();
   _parent_index = source.get_int16();
   _suggested_color.read_datagram(source);
-
-  if (source.get_remaining_size() > 0) {
-    _sort = source.get_int16();
-  }
+  _sort = source.get_int16();
+  _level_units = source.get_string();
+  _suggested_scale = source.get_float32();
 }

+ 3 - 0
panda/src/pstatclient/pStatCollectorDef.h

@@ -23,6 +23,7 @@ class EXPCL_PANDA PStatCollectorDef {
 public:
   PStatCollectorDef();
   PStatCollectorDef(int index, const string &name);
+  void set_parent(const PStatCollectorDef &parent);
 
   void write_datagram(Datagram &destination) const;
   void read_datagram(DatagramIterator &source);
@@ -32,6 +33,8 @@ public:
   int _parent_index;
   RGBColorf _suggested_color;
   int _sort;
+  string _level_units;
+  float _suggested_scale;
 };
 
 #endif

+ 111 - 29
panda/src/pstatclient/pStatFrameData.I

@@ -5,14 +5,36 @@
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatFrameData::is_empty
+//     Function: PStatFrameData::is_time_empty
 //       Access: Public
-//  Description: Returns true if there are no data points in the frame
+//  Description: Returns true if there are no time events in the frame
 //               data, false otherwise.
 ////////////////////////////////////////////////////////////////////
 INLINE bool PStatFrameData::
+is_time_empty() const {
+  return _time_data.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::is_level_empty
+//       Access: Public
+//  Description: Returns true if there are no levels indicated in the
+//               frame data, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool PStatFrameData::
+is_level_empty() const {
+  return _level_data.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::is_empty
+//       Access: Public
+//  Description: Returns true if the FrameData has no time or level
+//               data.
+////////////////////////////////////////////////////////////////////
+INLINE bool PStatFrameData::
 is_empty() const {
-  return _data.empty();
+  return is_time_empty() && is_level_empty();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -23,7 +45,8 @@ is_empty() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatFrameData::
 clear() {
-  _data.clear();
+  _time_data.clear();
+  _level_data.clear();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -33,11 +56,12 @@ clear() {
 //               data.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatFrameData::
-add_start(int index, double time) {
+add_start(int index, float time) {
+  nassertv((index & 0x7fff) == index);
   DataPoint dp;
   dp._index = index;
-  dp._time = time;
-  _data.push_back(dp);
+  dp._value = time;
+  _time_data.push_back(dp);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -47,11 +71,27 @@ add_start(int index, double time) {
 //               data.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatFrameData::
-add_stop(int index, double time) {
+add_stop(int index, float time) {
+  nassertv((index & 0x7fff) == index);
   DataPoint dp;
   dp._index = index | 0x8000;
-  dp._time = time;
-  _data.push_back(dp);
+  dp._value = time;
+  _time_data.push_back(dp);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::add_level
+//       Access: Public
+//  Description: Adds a particular level value associated with a given
+//               collector to the frame data.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatFrameData::
+add_level(int index, float level) {
+  nassertv((index & 0xffff) == index);
+  DataPoint dp;
+  dp._index = index;
+  dp._value = level;
+  _level_data.push_back(dp);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -61,11 +101,11 @@ add_stop(int index, double time) {
 //               data.  This will generally be the time of the start
 //               of the frame.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatFrameData::
+INLINE float PStatFrameData::
 get_start() const {
   nassertr(!is_empty(), 0.0);
 
-  return _data.front()._time;
+  return _time_data.front()._value;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -75,11 +115,11 @@ get_start() const {
 //               data.  This will generally be the time of the end
 //               of the frame.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatFrameData::
+INLINE float PStatFrameData::
 get_end() const {
   nassertr(!is_empty(), 0.0);
 
-  return _data.back()._time;
+  return _time_data.back()._value;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -87,11 +127,11 @@ get_end() const {
 //       Access: Public
 //  Description: Returns the total time elapsed for the frame.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatFrameData::
+INLINE float PStatFrameData::
 get_net_time() const {
   nassertr(!is_empty(), 0.0);
 
-  return _data.back()._time - _data.front()._time;
+  return _time_data.back()._value - _time_data.front()._value;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -102,23 +142,31 @@ get_net_time() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int PStatFrameData::
 get_num_events() const {
-  return _data.size();
+  return _time_data.size();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatFrameData::get_collector
+//     Function: PStatFrameData::get_time_collector
 //       Access: Public
 //  Description: Returns the index of the collector associated with
-//               the nth event.  If this is the first time the
-//               collector appears in the event list, it indicates the
-//               collector has started; if it is the second time, it
-//               indicates the collector has stopped.  Similarly for
-//               repeated appearances of the same collector.
+//               the nth event.
 ////////////////////////////////////////////////////////////////////
 INLINE int PStatFrameData::
-get_collector(int n) const {
-  nassertr(n >= 0 && n < (int)_data.size(), 0);
-  return _data[n]._index;
+get_time_collector(int n) const {
+  nassertr(n >= 0 && n < (int)_time_data.size(), 0);
+  return _time_data[n]._index & 0x7fff;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::is_start
+//       Access: Public
+//  Description: Returns true if the nth event represents a start
+//               event, or false if it represents a stop event.
+////////////////////////////////////////////////////////////////////
+INLINE bool PStatFrameData::
+is_start(int n) const {
+  nassertr(n >= 0 && n < (int)_time_data.size(), 0);
+  return (_time_data[n]._index & 0x8000) == 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -129,8 +177,42 @@ get_collector(int n) const {
 //               guaranteed to be shared among all events returned
 //               from a given client).
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatFrameData::
+INLINE float PStatFrameData::
 get_time(int n) const {
-  nassertr(n >= 0 && n < (int)_data.size(), 0);
-  return _data[n]._time;
+  nassertr(n >= 0 && n < (int)_time_data.size(), 0);
+  return _time_data[n]._value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::get_num_levels
+//       Access: Public
+//  Description: Returns the number of individual level values stored
+//               in the FrameData.
+////////////////////////////////////////////////////////////////////
+INLINE int PStatFrameData::
+get_num_levels() const {
+  return _level_data.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::get_level_collector
+//       Access: Public
+//  Description: Returns the index of the collector associated with
+//               the nth level value.
+////////////////////////////////////////////////////////////////////
+INLINE int PStatFrameData::
+get_level_collector(int n) const {
+  nassertr(n >= 0 && n < (int)_level_data.size(), 0);
+  return _level_data[n]._index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatFrameData::get_level
+//       Access: Public
+//  Description: Returns the height of the nth level value.
+////////////////////////////////////////////////////////////////////
+INLINE float PStatFrameData::
+get_level(int n) const {
+  nassertr(n >= 0 && n < (int)_level_data.size(), 0);
+  return _level_data[n]._value;
 }

+ 23 - 5
panda/src/pstatclient/pStatFrameData.cxx

@@ -17,9 +17,15 @@
 void PStatFrameData::
 write_datagram(Datagram &destination) const {
   Data::const_iterator di;
-  for (di = _data.begin(); di != _data.end(); ++di) {
+  destination.add_uint16(_time_data.size());
+  for (di = _time_data.begin(); di != _time_data.end(); ++di) {
     destination.add_uint16((*di)._index);
-    destination.add_float64((*di)._time);
+    destination.add_float32((*di)._value);
+  }
+  destination.add_uint16(_level_data.size());
+  for (di = _level_data.begin(); di != _level_data.end(); ++di) {
+    destination.add_uint16((*di)._index);
+    destination.add_float32((*di)._value);
   }
 }
 
@@ -31,11 +37,23 @@ write_datagram(Datagram &destination) const {
 void PStatFrameData::
 read_datagram(DatagramIterator &source) {
   clear();
-  while (source.get_remaining_size() > 0) {
+
+  int i;
+  int time_size = source.get_uint16();
+  for (i = 0; i < time_size; i++) {
+    nassertv(source.get_remaining_size() > 0);
+    DataPoint dp;
+    dp._index = source.get_uint16();
+    dp._value = source.get_float32();
+    _time_data.push_back(dp);
+  }
+  int level_size = source.get_uint16();
+  for (i = 0; i < level_size; i++) {
+    nassertv(source.get_remaining_size() > 0);
     DataPoint dp;
     dp._index = source.get_uint16();
-    dp._time = source.get_float64();
-    _data.push_back(dp);
+    dp._value = source.get_float32();
+    _level_data.push_back(dp);
   }
 }
 

+ 22 - 10
panda/src/pstatclient/pStatFrameData.h

@@ -17,22 +17,34 @@ class DatagramIterator;
 
 ////////////////////////////////////////////////////////////////////
 // 	 Class : PStatFrameData
-// Description : Defines the raw timing data for a single frame.
+// Description : Contains the raw timing and level data for a single
+//               frame.  This is a sequence of start/stop events, as
+//               well as a table of level values, associated with a
+//               number of collectors within a single frame.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA PStatFrameData {
 public:
+  INLINE bool is_time_empty() const;
+  INLINE bool is_level_empty() const;
   INLINE bool is_empty() const;
   INLINE void clear();
-  INLINE void add_start(int index, double time);
-  INLINE void add_stop(int index, double time);
 
-  INLINE double get_start() const;
-  INLINE double get_end() const;
-  INLINE double get_net_time() const;
+  INLINE void add_start(int index, float time);
+  INLINE void add_stop(int index, float time);
+  INLINE void add_level(int index, float level);
+
+  INLINE float get_start() const;
+  INLINE float get_end() const;
+  INLINE float get_net_time() const;
 
   INLINE int get_num_events() const;
-  INLINE int get_collector(int n) const;
-  INLINE double get_time(int n) const;
+  INLINE int get_time_collector(int n) const;
+  INLINE bool is_start(int n) const;
+  INLINE float get_time(int n) const;
+
+  INLINE int get_num_levels() const;
+  INLINE int get_level_collector(int n) const;
+  INLINE float get_level(int n) const;
 
   void write_datagram(Datagram &destination) const;
   void read_datagram(DatagramIterator &source);
@@ -41,11 +53,11 @@ private:
   class DataPoint {
   public:
     int _index;
-    double _time;
+    float _value;
   };
   typedef vector<DataPoint> Data;
 
-  Data _data;
+  Data _time_data, _level_data;
 };
 
 #include "pStatFrameData.I"

+ 127 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -0,0 +1,127 @@
+// Filename: pStatProperties.cxx
+// Created by:  drose (17May01)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "pStatProperties.h"
+#include "pStatCollectorDef.h"
+#include "pStatClient.h"
+
+#ifdef DO_PSTATS
+
+////////////////////////////////////////////////////////////////////
+//
+// This file defines the predefined properties (color, sort, etc.) for
+// the various PStatCollectors that may be defined within Panda or
+// even elsewhere.
+//
+// It is a little strange to defined these properties here instead of
+// where the collectors are actually declared, but it's handy to have
+// them all in one place, so we can easily see which colors are
+// available, etc.  It also makes the declarations a lot simpler,
+// since there are quite a few esoteric parameters to specify.
+//
+// We could define these in some external data file that is read in at
+// runtime, so that you could extend this list without having to
+// relink panda, but then there are the usual problems with ensuring
+// that the file is available to you at runtime.  The heck with it.
+//
+// At least, no other file depends on this file, so it may be modified
+// without forcing anything else to be recompiled.
+//
+////////////////////////////////////////////////////////////////////
+
+struct ColorDef {
+  float r, g, b;
+};
+
+struct TimeCollectorProperties {
+  const char *name;
+  ColorDef color;
+  float suggested_scale;
+};
+
+struct LevelCollectorProperties {
+  const char *name;
+  ColorDef color;
+  const char *units;
+  float suggested_scale;
+};
+
+static TimeCollectorProperties time_properties[] = {
+  { "App",                              { 0.0, 1.0, 1.0 },  1.0 / 30.0 },
+  { "App:Animation",                    { 1.0, 0.0, 1.0 } },
+  { "App:Collisions",                   { 1.0, 0.5, 0.0 } },
+  { "App:Data graph",                   { 0.5, 0.8, 0.4 } },
+  { "App:Show code",                    { 0.8, 0.2, 1.0 } },
+  { "Cull",                             { 0.0, 1.0, 0.0 },  1.0 / 30.0 },
+  { "Draw",                             { 1.0, 0.0, 0.0 },  1.0 / 30.0 },
+  { "Draw:Swap buffers",                { 0.5, 1.0, 0.8 } },
+  { "Draw:Clear",                       { 0.5, 0.7, 0.7 } },
+  { "Draw:Show fps",                    { 0.5, 0.8, 1.0 } },
+  { "Draw:Make current",                { 1.0, 0.6, 0.3 } },
+  { NULL }
+};
+
+static LevelCollectorProperties level_properties[] = {
+  { "Texture usage",                    { 1.0, 0.0, 0.0 },  "MB", 8.0 },
+  { "Texture usage:Active",             { 1.0, 1.0, 0.0 } },
+  { "Texture memory",                   { 0.0, 0.0, 1.0 },  "MB", 8.0 },
+  { "Texture memory:In use",            { 0.0, 1.0, 1.0 } },
+  { NULL }
+};
+
+    
+////////////////////////////////////////////////////////////////////
+//     Function: initialize_collector_def
+//  Description: This is the only accessor function into this table.
+//               The PStatCollectorDef constructor calls it when a new
+//               PStatCollectorDef is created.  It should look up in
+//               the table and find a matching definition for this def
+//               by name; if one is found, the properties are applied.
+////////////////////////////////////////////////////////////////////
+void
+initialize_collector_def(PStatClient *client, PStatCollectorDef *def) {
+  int i;
+  string fullname;
+
+  if (def->_index == 0) {
+    fullname = def->_name;
+  } else {
+    fullname = client->get_collector_fullname(def->_index);
+  }
+
+  for (i = 0;
+       time_properties[i].name != (const char *)NULL; 
+       i++) {
+    const TimeCollectorProperties &tp = time_properties[i];
+    if (fullname == tp.name) {
+      def->_sort = i;
+      def->_suggested_color.set(tp.color.r, tp.color.g, tp.color.b);
+      if (tp.suggested_scale != 0.0) {
+        def->_suggested_scale = tp.suggested_scale;
+      }
+      return;
+    }
+  }
+
+  for (i = 0;
+       level_properties[i].name != (const char *)NULL; 
+       i++) {
+    const LevelCollectorProperties &lp = level_properties[i];
+    if (fullname == lp.name) {
+      def->_sort = i;
+      def->_suggested_color.set(lp.color.r, lp.color.g, lp.color.b);
+      if (lp.suggested_scale != 0.0) {
+        def->_suggested_scale = lp.suggested_scale;
+      }
+      if (lp.units != (const char *)NULL) {
+        def->_level_units = lp.units;
+      }
+      return;
+    }
+  }
+}
+
+
+#endif // DO_PSTATS

+ 21 - 0
panda/src/pstatclient/pStatProperties.h

@@ -0,0 +1,21 @@
+// Filename: pStatProperties.h
+// Created by:  drose (17May01)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PSTATPROPERTIES_H
+#define PSTATPROPERTIES_H
+
+#include <pandabase.h>
+
+#ifdef DO_PSTATS
+
+class PStatClient;
+class PStatCollectorDef;
+
+void initialize_collector_def(PStatClient *client, PStatCollectorDef *def);
+
+#endif  // DO_PSTATS
+
+#endif
+

+ 52 - 41
panda/src/pstatclient/test_client.cxx

@@ -27,33 +27,34 @@ void signal_handler(int) {
 
 struct SampleData {
   const char *category;
-  RGBColorf color;
-  int min_ms;
-  int max_ms;
+  float min_ms;
+  float max_ms;
+  bool is_level;
 };
 
 SampleData dataset_zero[] = {
-  { "Draw", RGBColorf(0,0,1), 10, 10 },
-  { "Cull", RGBColorf(0,1,1), 5, 6 },
-  { "App", RGBColorf(1,0,0), 0, 5 },
+  { "Draw", 10, 10, false },
+  { "Cull", 5, 6, false },
+  { "App", 0, 5, false },
+  { "Texture memory", 1, 0.01, true },
   { NULL },
 };
 
 SampleData dataset_one[] = {
-  { "Draw", RGBColorf(0,0,1), 10, 12 },
-  { "Squeak", RGBColorf(1,1,0), 25, 30 },
+  { "Draw", 10, 12, false },
+  { "Squeak", 25, 30, false },
   { NULL },
 };
 
 SampleData dataset_two[] = {
-  { "Squeak", RGBColorf(1,1,0), 40, 45 },
-  { "Cull", RGBColorf(0,1,1), 20, 22 },
-  { "Draw", RGBColorf(0,0,1), 10, 20 },
-  { "Animation", RGBColorf(0.5,1,0.5), 0, 0 },
-  { "Animation:mickey", RGBColorf(0,0,0), 5, 6 },
-  { "Animation:donald", RGBColorf(0,0,0), 5, 6 },
-  { "Animation:goofy", RGBColorf(0,0,0), 5, 6 },
-  { "Animation:pluto", RGBColorf(0,0,0), 5, 6 },
+  { "Squeak", 40, 45, false },
+  { "Cull", 20, 22, false },
+  { "Draw", 10, 20, false },
+  { "Animation", 0, 0, false },
+  { "Animation:mickey", 5, 6, false },
+  { "Animation:donald", 5, 6, false },
+  { "Animation:goofy", 5, 6, false },
+  { "Animation:pluto", 5, 6, false },
   { NULL },
 };
 
@@ -65,7 +66,7 @@ SampleData *datasets[NUM_DATASETS] = {
 
 class WaitRequest {
 public:
-  double _time;
+  float _time;
   int _index;
   bool _start;
   
@@ -111,10 +112,10 @@ main(int argc, char *argv[]) {
     ds_index = atoi(argv[3]);
   } else {
     // Pick a random Dataset.
-    ds_index = (int)((double)NUM_DATASETS * rand() / (RAND_MAX + 1.0));
+    ds_index = (int)((float)NUM_DATASETS * rand() / (RAND_MAX + 1.0));
   }
   if (ds_index < 0 || ds_index >= NUM_DATASETS) {
-    nout << "Invalid datasets; choose a number in the range 0 to "
+    nout << "Invalid dataset; choose a number in the range 0 to "
 	 << NUM_DATASETS - 1 << "\n";
     exit(1);
   }
@@ -124,39 +125,49 @@ main(int argc, char *argv[]) {
   vector<PStatCollector> _collectors;
   int i = 0;
   while (ds[i].category != (const char *)NULL) {
-    _collectors.push_back(PStatCollector(ds[i].category, ds[i].color, i));
+    _collectors.push_back(PStatCollector(ds[i].category));
+    if (ds[i].is_level) {
+      _collectors[i].set_level(ds[i].min_ms);
+    }
     i++;
   }
   
   while (!user_interrupted && client->is_connected()) {
     client->get_main_thread().new_frame();
 
-    double total_ms = 0.0;
-    double now = client->get_clock().get_real_time();
+    float total_ms = 0.0;
+    float now = client->get_clock().get_real_time();
 
     typedef vector<WaitRequest> Wait;
     Wait wait;
 
     // Make up some random intervals to "wait".
-    for (i = 0; i < _collectors.size(); i++) {
-      // A bit of random jitter so the collectors might overlap some.
-      double jitter_ms = (5.0 * rand() / (RAND_MAX + 1.0));
-
-      WaitRequest wr;
-      wr._time = now + jitter_ms / 1000.0;
-      wr._index = i;
-      wr._start = true;
-      wait.push_back(wr);
-
-      int ms_range = ds[i].max_ms - ds[i].min_ms;
-      double ms = (double)ds[i].min_ms + 
-	((double)ms_range * rand() / (RAND_MAX + 1.0));
-      now += ms / 1000.0;
-      total_ms += ms;
-
-      wr._time = now + jitter_ms / 1000.0;
-      wr._start = false;
-      wait.push_back(wr);
+    for (i = 0; i < (int)_collectors.size(); i++) {
+      if (ds[i].is_level) {
+        // Make up an amount to add/delete to the level this frame.
+        float increment = ds[i].max_ms * (rand() / (RAND_MAX + 1.0) - 0.5);
+        _collectors[i].add_level(increment);
+
+      } else {
+        // A bit of random jitter so the collectors might overlap some.
+        float jitter_ms = (5.0 * rand() / (RAND_MAX + 1.0));
+        
+        WaitRequest wr;
+        wr._time = now + jitter_ms / 1000.0;
+        wr._index = i;
+        wr._start = true;
+        wait.push_back(wr);
+        
+        float ms_range = ds[i].max_ms - ds[i].min_ms;
+        float ms = (float)ds[i].min_ms + 
+          (ms_range * rand() / (RAND_MAX + 1.0));
+        now += ms / 1000.0;
+        total_ms += ms;
+        
+        wr._time = now + jitter_ms / 1000.0;
+        wr._start = false;
+        wait.push_back(wr);
+      }        
     }
 
     // Put the wait requests in order, to allow for the jitter, and

+ 1 - 1
panda/src/sgraphutil/directRenderTraverser.cxx

@@ -30,7 +30,7 @@
 TypeHandle DirectRenderTraverser::_type_handle;
 
 #ifndef CPPPARSER
-PStatCollector DirectRenderTraverser::_draw_pcollector("Draw", RGBColorf(1,0,0), 20);
+PStatCollector DirectRenderTraverser::_draw_pcollector("Draw");
 #endif
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
pandatool/src/gtk-stats/gtkStatsGuide.cxx

@@ -24,7 +24,7 @@ GtkStatsGuide(PStatStripChart *chart) :
     get_style()->gtkobj()->fg_gc[GTK_WIDGET_STATE (GTK_WIDGET(gtkobj()))];
 
   Gdk_Font font = fg_gc.get_font();
-  int text_width = font.string_width("000");
+  int text_width = font.string_width("0000");
   set_usize(text_width, 0);
 }
 

+ 1 - 1
pandatool/src/gtk-stats/gtkStatsLabel.cxx

@@ -28,7 +28,7 @@ GtkStatsLabel(PStatMonitor *monitor, int collector_index,
   _bg_color.set_rgb_p(rgb[0], rgb[1], rgb[2]);
 
   // Should our foreground be black or white?
-  double bright = 
+  float bright = 
     rgb[0] * 0.299 +
     rgb[1] * 0.587 +
     rgb[2] * 0.114;

+ 19 - 1
pandatool/src/gtk-stats/gtkStatsMonitor.cxx

@@ -20,6 +20,7 @@
 GtkStatsMonitor::
 GtkStatsMonitor() {
   _destructing = false;
+  _new_collector = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -81,7 +82,7 @@ get_monitor_name() {
 void GtkStatsMonitor::
 initialized() {
   // Create a default window: a strip chart for the main thread.
-  new GtkStatsStripWindow(this, 0, 0, 400, 100);
+  new GtkStatsStripWindow(this, 0, 0, false, 400, 100);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -99,6 +100,22 @@ got_hello() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsMonitor::new_collector
+//       Access: Public, Virtual
+//  Description: Called whenever a new Collector definition is
+//               received from the client.  Generally, the client will
+//               send all of its collectors over shortly after
+//               connecting, but there's no guarantee that they will
+//               all be received before the first frames are received.
+//               The monitor should be prepared to accept new Collector
+//               definitions midstream.
+////////////////////////////////////////////////////////////////////
+void GtkStatsMonitor::
+new_collector(int) {
+  _new_collector = true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsMonitor::new_data
 //       Access: Public, Virtual
@@ -142,6 +159,7 @@ idle() {
   for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
     (*wi)->idle();
   }
+  _new_collector = false;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 0
pandatool/src/gtk-stats/gtkStatsMonitor.h

@@ -32,6 +32,7 @@ public:
 
   virtual void initialized();
   virtual void got_hello();
+  virtual void new_collector(int collector_index);
   virtual void new_data(int thread_index, int frame_number);
   virtual void lost_connection();
   virtual void idle();
@@ -46,6 +47,7 @@ public:
   Windows _windows;
 
   bool _destructing;
+  bool _new_collector;
 };
 
 #endif

+ 2 - 2
pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx

@@ -115,7 +115,7 @@ begin_draw() {
   int num_guide_bars = get_num_guide_bars();
   for (int i = 0; i < num_guide_bars; i++) {
     const GuideBar &bar = get_guide_bar(i);
-    int x = (int)((double)get_xsize() * bar._height / get_horizontal_scale());
+    int x = (int)((float)get_xsize() * bar._height / get_horizontal_scale());
 
     if (x >= 5 && x <= get_xsize() - 5) {
       // Only draw it if it's not too close to either edge.
@@ -162,7 +162,7 @@ end_draw() {
   int num_guide_bars = get_num_guide_bars();
   for (int i = 0; i < num_guide_bars; i++) {
     const GuideBar &bar = get_guide_bar(i);
-    int x = (int)((double)get_xsize() * bar._height / get_horizontal_scale());
+    int x = (int)((float)get_xsize() * bar._height / get_horizontal_scale());
 
     if (x >= 5 && x <= get_xsize() - 5) {
       // Only draw it if it's not too close to either edge.

+ 10 - 9
pandatool/src/gtk-stats/gtkStatsPianoWindow.cxx

@@ -46,6 +46,7 @@ mark_dead() {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsPianoWindow::
 idle() {
+  GtkStatsWindow::idle();
   _chart->update();
 }
 
@@ -62,28 +63,28 @@ setup_menu() {
 
   scale_menu->items().push_back
     (MenuElem("0.1 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 0.1)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 0.1f)));
   scale_menu->items().push_back
     (MenuElem("1 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 1.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 1.0f)));
   scale_menu->items().push_back
     (MenuElem("5 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 5.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 5.0f)));
   scale_menu->items().push_back
     (MenuElem("10 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 10.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 10.0f)));
   scale_menu->items().push_back
     (MenuElem("20 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 20.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 20.0f)));
   scale_menu->items().push_back
     (MenuElem("30 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 30.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 30.0f)));
   scale_menu->items().push_back
     (MenuElem("60 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 60.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 60.0f)));
   scale_menu->items().push_back
     (MenuElem("120 Hz",
-	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 120.0)));
+	      bind(slot(this, &GtkStatsPianoWindow::menu_hscale), 120.0f)));
 
   _menu->items().push_back(MenuElem("Scale", *manage(scale_menu)));
 }
@@ -108,7 +109,7 @@ menu_new_window() {
 //               The units is in Hz.
 ////////////////////////////////////////////////////////////////////
 void GtkStatsPianoWindow::
-menu_hscale(double hz) {
+menu_hscale(float hz) {
   _chart->set_horizontal_scale(1.0 / hz);
 }
 

+ 1 - 1
pandatool/src/gtk-stats/gtkStatsPianoWindow.h

@@ -28,7 +28,7 @@ public:
 protected:
   virtual void setup_menu();
   virtual void menu_new_window();
-  void menu_hscale(double hz);
+  void menu_hscale(float hz);
 
 private:
   void layout_window(int chart_xsize, int chart_ysize);

+ 11 - 2
pandatool/src/gtk-stats/gtkStatsStripChart.cxx

@@ -174,13 +174,22 @@ draw_slice(int x, int frame_number) {
   // Start by clearing the band first.
   _pixmap.draw_line(_white_gc, x, 0, x, get_ysize());
 
-  double overall_time = 0.0;
+  float overall_time = 0.0;
   int y = get_ysize();
 
   FrameData::const_iterator fi;
   for (fi = frame.begin(); fi != frame.end(); ++fi) {
     const ColorData &cd = (*fi);
-    overall_time += cd._net_time;
+    overall_time += cd._net_value;
+
+    if (overall_time > get_vertical_scale()) {
+      // Off the top.  Go ahead and clamp it by hand, in case it's so
+      // far off the top we'd overflow the 16-bit pixel value.
+      _pixmap.draw_line(get_collector_gc(cd._collector_index), x, y, x, 0);
+      // And we can consider ourselves done now.
+      return;
+    }
+      
     int top_y = height_to_pixel(overall_time);
     _pixmap.draw_line(get_collector_gc(cd._collector_index), x, y, x, top_y);
     y = top_y;

+ 1 - 1
pandatool/src/gtk-stats/gtkStatsStripChart.h

@@ -37,7 +37,7 @@ public:
 
   Gdk_GC get_collector_gc(int collector_index);
 
-  // This signal is thrown when the user double-clicks on a label or
+  // This signal is thrown when the user float-clicks on a label or
   // on a band of color.
   SigC::Signal1<void, int> collector_picked;
 

+ 185 - 48
pandatool/src/gtk-stats/gtkStatsStripWindow.cxx

@@ -7,7 +7,9 @@
 #include "gtkStatsStripChart.h"
 #include "gtkStatsGuide.h"
 
+#include <pStatCollectorDef.h>
 #include <string_utils.h>
+
 #include <stdio.h>  // for sprintf
 
 
@@ -21,15 +23,20 @@ using Gtk::Menu_Helpers::SeparatorElem;
 ////////////////////////////////////////////////////////////////////
 GtkStatsStripWindow::
 GtkStatsStripWindow(GtkStatsMonitor *monitor, int thread_index, 
-		    int collector_index, int chart_xsize, int chart_ysize) :
+		    int collector_index, bool show_level,
+                    int chart_xsize, int chart_ysize) :
   GtkStatsWindow(monitor),
   _thread_index(thread_index),
-  _collector_index(collector_index)
+  _collector_index(collector_index),
+  _show_level(show_level)
 {
   _title_unknown = false;
+  _setup_scale_menu = false;
 
   setup_menu();
   layout_window(chart_xsize, chart_ysize);
+
+  new_collector();  // To set up the menus in case we can.
   show();
 }
 
@@ -47,6 +54,40 @@ mark_dead() {
   _chart->mark_dead();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripWindow::new_collector
+//       Access: Public, Virtual
+//  Description: Called when a new collector has become known.
+//
+//               For the GtkStatsStripWindow, this forces a rebuild of
+//               the menu that selects the collectors available for
+//               picking levels.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripWindow::
+new_collector() {
+  const PStatClientData *client_data = _monitor->get_client_data();
+  _levels_menu->items().clear();
+
+  int num_collectors = client_data->get_num_collectors();
+  for (int i = 0; i < num_collectors; i++) {
+    if (client_data->has_collector(i) &&
+        client_data->get_collector_has_level(i)) {
+      const PStatCollectorDef &def =
+        client_data->get_collector_def(i);
+
+      // Normally, we only put top-level entries on the menu.  The
+      // lower entries can take care of themselves.
+      if (def._parent_index == 0) {
+        _levels_menu->items().push_back
+          (MenuElem(client_data->get_collector_name(i),
+                    bind(slot(this, &GtkStatsStripWindow::menu_show_levels), i)));
+      }
+    }
+  }
+
+  setup_scale_menu();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsStripWindow::idle
 //       Access: Public, Virtual
@@ -54,11 +95,12 @@ mark_dead() {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripWindow::
 idle() {
+  GtkStatsWindow::idle();
   _chart->update();
 
   const PStatThreadData *thread_data = _chart->get_view().get_thread_data();
   if (!thread_data->is_empty()) {
-    double frame_rate = thread_data->get_frame_rate();
+    float frame_rate = thread_data->get_frame_rate();
     char buffer[128];
     sprintf(buffer, "Frame rate: %0.1f Hz", frame_rate); 
     _frame_rate_label->set_text(buffer);
@@ -82,51 +124,102 @@ setup_menu() {
     
   speed_menu->items().push_back
     (MenuElem("1",  // 1 chart width scrolls by per minute.
-	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 1.0)));
+	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 1.0f)));
   speed_menu->items().push_back
     (MenuElem("2",  // 2 chart widths scroll by per minute.
-	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 2.0)));
+	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 2.0f)));
   speed_menu->items().push_back
     (MenuElem("3", 
-	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 3.0)));
+	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 3.0f)));
   speed_menu->items().push_back
     (MenuElem("6", 
-	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 6.0)));
+	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 6.0f)));
   speed_menu->items().push_back
     (MenuElem("12", 
-	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 12.0)));
+	      bind(slot(this, &GtkStatsStripWindow::menu_hscale), 12.0f)));
 
   _menu->items().push_back(MenuElem("Speed", *manage(speed_menu)));
 
 
-  Gtk::Menu *scale_menu = new Gtk::Menu;
-
-  scale_menu->items().push_back
-    (MenuElem("10000 ms (0.1 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 0.1)));
-  scale_menu->items().push_back
-    (MenuElem("1000 ms (1 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 1.0)));
-  scale_menu->items().push_back
-    (MenuElem("200 ms (5 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 5.0)));
-  scale_menu->items().push_back
-    (MenuElem("100 ms (10 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 10.0)));
-  scale_menu->items().push_back
-    (MenuElem("50.0 ms (20 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 20.0)));
-  scale_menu->items().push_back
-    (MenuElem("33.3 ms (30 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 30.0)));
-  scale_menu->items().push_back
-    (MenuElem("16.7 ms (60 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 60.0)));
-  scale_menu->items().push_back
-    (MenuElem("8.3 ms (120 Hz)",
-	      bind(slot(this, &GtkStatsStripWindow::menu_vscale), 120.0)));
-
-  _menu->items().push_back(MenuElem("Scale", *manage(scale_menu)));
+  _scale_menu = new Gtk::Menu;
+  _scale_menu->items().push_back
+    (MenuElem("Auto scale",
+	      slot(this, &GtkStatsStripWindow::menu_auto_vscale)));
+
+  _menu->items().push_back(MenuElem("Scale", *manage(_scale_menu)));
+
+  _levels_menu = new Gtk::Menu;
+  _menu->items().push_back(MenuElem("Levels", *manage(_levels_menu)));
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripWindow::setup_scale_menu
+//       Access: Protected
+//  Description: Sets up the options on the scale menu.  We can't do
+//               this until we have initialized the _chart member and
+//               we have gotten our first collector_index.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripWindow::
+setup_scale_menu() {
+  if (_setup_scale_menu) {
+    // Already done it.
+    return;
+  }
+
+  const PStatClientData *client_data = _monitor->get_client_data();
+  if (!client_data->has_collector(_collector_index)) {
+    // Can't set up the scale menu yet.
+    return;
+  }
+
+  const PStatCollectorDef &def = client_data->get_collector_def(_collector_index);
+  float base_scale = 1.0;
+  string unit_name = def._level_units;
+
+  if (_show_level) {
+    _chart->set_guide_bar_unit_name(unit_name);
+    _chart->set_guide_bar_units(PStatGraph::GBU_named);
+
+  } else {
+    _chart->set_guide_bar_units(PStatGraph::GBU_ms);
+  }
+
+  if (def._suggested_scale != 0.0) {
+    base_scale = def._suggested_scale;
+  } else if (!_show_level) {
+    base_scale = 1.0 / _chart->get_target_frame_rate();
+  }
+
+  static const float scales[] = {
+    50.0,
+    10.0,
+    5.0,
+    2.0,
+    1.0,
+    0.5,
+    0.2,
+    0.1,
+    0.02,
+  };
+  static const int num_scales = sizeof(scales) / sizeof(float);
+
+  for (int i = 0; i < num_scales; i++) {
+    float scale = base_scale * scales[i];
+    string label;
+
+    if (_show_level) {
+      label = _chart->format_number(scale, PStatGraph::GBU_named | PStatGraph::GBU_show_units, unit_name);
+    } else {
+      label = _chart->format_number(scale, PStatGraph::GBU_ms | PStatGraph::GBU_hz | PStatGraph::GBU_show_units);
+    }
+    
+    _scale_menu->items().push_back
+      (MenuElem(label,
+                bind(slot(this, &GtkStatsStripWindow::menu_vscale), scale)));
+  }
+
+  _setup_scale_menu = true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -136,8 +229,9 @@ setup_menu() {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripWindow::
 menu_new_window() {
-  new GtkStatsStripWindow(_monitor, _thread_index, _collector_index,
-			  _chart->get_xsize(), _chart->get_ysize());
+  new GtkStatsStripWindow(_monitor, _thread_index, _collector_index, 
+                          _show_level,
+                          _chart->get_xsize(), _chart->get_ysize());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -151,7 +245,7 @@ menu_new_window() {
 //               The units is in chart width per minute.
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripWindow::
-menu_hscale(double wpm) {
+menu_hscale(float wpm) {
   _chart->set_horizontal_scale(60.0 / wpm);
 }
 
@@ -161,11 +255,36 @@ menu_hscale(double wpm) {
 //  Description: Selects a new vertical scale for the strip chart.
 //               This is done from the menu called "Scale".
 //
-//               The units is in Hz.
+//               The units is in seconds, or whatever the units
+//               of choice are.
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripWindow::
-menu_vscale(double hz) {
-  _chart->set_vertical_scale(1.0 / hz);
+menu_vscale(float max_height) {
+  _chart->set_vertical_scale(max_height);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripWindow::menu_auto_vscale
+//       Access: Protected
+//  Description: Selects a suitable vertical scale based on the data
+//               already visible in the chart.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripWindow::
+menu_auto_vscale() {
+  _chart->set_auto_vertical_scale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripWindow::menu_show_levels
+//       Access: Protected
+//  Description: Shows the level values known for the indicated
+//               collector.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripWindow::
+menu_show_levels(int collector_index) {
+  new GtkStatsStripWindow(_monitor, _thread_index, collector_index, 
+                          true,
+			  _chart->get_xsize(), _chart->get_ysize());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -180,7 +299,8 @@ menu_vscale(double hz) {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripWindow::
 open_subchart(int collector_index) {
-  new GtkStatsStripWindow(_monitor, _thread_index, collector_index,
+  new GtkStatsStripWindow(_monitor, _thread_index, collector_index, 
+                          _show_level,
 			  _chart->get_xsize(), _chart->get_ysize());
 }
 
@@ -224,10 +344,18 @@ layout_window(int chart_xsize, int chart_ysize) {
   frame->show();
   chart_table->attach(*manage(frame), 1, 2, 1, 2);
 
-  _chart = new GtkStatsStripChart(_monitor, 
-				  _monitor->get_view(_thread_index), 
-				  _collector_index,
-				  chart_xsize, chart_ysize);
+  if (_show_level) {
+    _chart = new GtkStatsStripChart(_monitor, 
+                                    _monitor->get_level_view(_collector_index, _thread_index), 
+                                    _collector_index,
+                                    chart_xsize, chart_ysize);
+  } else {
+    _chart = new GtkStatsStripChart(_monitor, 
+                                    _monitor->get_view(_thread_index), 
+                                    _collector_index,
+                                    chart_xsize, chart_ysize);
+  }
+
   _chart->collector_picked.
     connect(slot(this, &GtkStatsStripWindow::open_subchart));
   frame->add(*manage(_chart));
@@ -254,7 +382,16 @@ get_title_text() {
 
   const PStatClientData *client_data = _monitor->get_client_data();
   if (client_data->has_collector(_collector_index)) {
-    text = client_data->get_collector_name(_collector_index) + " time";
+    const PStatCollectorDef &def = client_data->get_collector_def(_collector_index);
+    if (_show_level) {
+      if (def._level_units.empty()) {
+        text = def._name;
+      } else {
+        text = def._name + " (" + def._level_units + ")";
+      }
+    } else {
+      text = def._name + " time";
+    }
   } else {
     _title_unknown = true;
   }

+ 13 - 3
pandatool/src/gtk-stats/gtkStatsStripWindow.h

@@ -21,16 +21,21 @@ class GtkStatsStripChart;
 class GtkStatsStripWindow : public GtkStatsWindow {
 public:
   GtkStatsStripWindow(GtkStatsMonitor *monitor, int thread_index, 
-		      int collector_index, int chart_xsize, int chart_ysize);
+		      int collector_index, bool show_level,
+                      int chart_xsize, int chart_ysize);
 
   virtual void mark_dead();
+  virtual void new_collector();
   virtual void idle();
 
 protected:
   virtual void setup_menu();
+  void setup_scale_menu();
   virtual void menu_new_window();
-  void menu_hscale(double wpm);
-  void menu_vscale(double hz);
+  void menu_hscale(float wpm);
+  void menu_vscale(float max_height);
+  void menu_auto_vscale();
+  void menu_show_levels(int collector_index);
   void open_subchart(int collector_index);
 
 private:
@@ -40,11 +45,16 @@ private:
 private:
   int _thread_index;
   int _collector_index;
+  bool _show_level;
   bool _title_unknown;
+  bool _setup_scale_menu;
   
   Gtk::Label *_title_label;
   Gtk::Label *_frame_rate_label;
   GtkStatsStripChart *_chart;
+
+  Gtk::Menu *_scale_menu;
+  Gtk::Menu *_levels_menu;
 };
 
 

+ 5 - 12
pandatool/src/gtk-stats/gtkStatsWindow.cxx

@@ -81,17 +81,7 @@ mark_dead() {
 //               the window cares.
 ////////////////////////////////////////////////////////////////////
 void GtkStatsWindow::
-new_collector(int) {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GtkStatsWindow::new_thread
-//       Access: Public, Virtual
-//  Description: Called when a new thread has become known, in case
-//               the window cares.
-////////////////////////////////////////////////////////////////////
-void GtkStatsWindow::
-new_thread(int) {
+new_collector() {
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -101,6 +91,9 @@ new_thread(int) {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsWindow::
 idle() {
+  if (_monitor->_new_collector) {
+    new_collector();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -150,7 +143,7 @@ setup_menu() {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsWindow::
 menu_open_strip_chart() {
-  new GtkStatsStripWindow(_monitor, 0, 0, 400, 100);
+  new GtkStatsStripWindow(_monitor, 0, 0, false, 400, 100);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 2
pandatool/src/gtk-stats/gtkStatsWindow.h

@@ -30,8 +30,7 @@ public:
 
   virtual void update_title();
   virtual void mark_dead();
-  virtual void new_collector(int collector_index);
-  virtual void new_thread(int thread_index);
+  virtual void new_collector();
   virtual void idle();
 
 protected:

+ 58 - 16
pandatool/src/pstatserver/pStatClientData.cxx

@@ -33,7 +33,7 @@ PStatClientData::
 ~PStatClientData() {
   Collectors::const_iterator ci;
   for (ci = _collectors.begin(); ci != _collectors.end(); ++ci) {
-    delete (*ci);
+    delete (*ci)._def;
   }
 }
 
@@ -85,7 +85,7 @@ get_num_collectors() const {
 bool PStatClientData::
 has_collector(int index) const {
   return (index >= 0 && index < (int)_collectors.size() &&
-	  _collectors[index] != (PStatCollectorDef *)NULL);
+	  _collectors[index]._def != (PStatCollectorDef *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -98,7 +98,7 @@ get_collector_def(int index) const {
   if (!has_collector(index)) {
     return _null_collector;
   }
-  return *_collectors[index];
+  return *_collectors[index]._def;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -111,7 +111,7 @@ get_collector_name(int index) const {
   if (!has_collector(index)) {
     return "Unknown";
   }
-  const PStatCollectorDef *def = _collectors[index];
+  const PStatCollectorDef *def = _collectors[index]._def;
   return def->_name;
 }
 
@@ -129,7 +129,7 @@ get_collector_fullname(int index) const {
     return "Unknown";
   }
 
-  const PStatCollectorDef *def = _collectors[index];
+  const PStatCollectorDef *def = _collectors[index]._def;
   if (def->_parent_index == 0) {
     return def->_name;
   } else {
@@ -137,6 +137,34 @@ get_collector_fullname(int index) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClientData::set_collector_has_level
+//       Access: Public
+//  Description: Indicates whether the given collector has level data
+//               (and consequently, whether it should appear on the
+//               Levels menu).
+////////////////////////////////////////////////////////////////////
+void PStatClientData::
+set_collector_has_level(int index, bool flag) {
+  slot_collector(index);
+  nassertv(index >= 0 && index < (int)_collectors.size());
+  _collectors[index]._is_level = flag;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClientData::get_collector_has_level
+//       Access: Public
+//  Description: Returns whether the given collector has level data
+//               (and consequently, whether it should appear on the
+//               Levels menu).
+////////////////////////////////////////////////////////////////////
+bool PStatClientData::
+get_collector_has_level(int index) const {
+  return (index >= 0 && index < (int)_collectors.size() &&
+	  _collectors[index]._is_level);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatClientData::get_num_threads
 //       Access: Public
@@ -228,20 +256,15 @@ get_child_distance(int parent, int child) const {
 ////////////////////////////////////////////////////////////////////
 void PStatClientData::
 add_collector(PStatCollectorDef *def) {
-  // A sanity check on the index number.
-  nassertv(def->_index < 1000);
+  slot_collector(def->_index);
+  nassertv(def->_index >= 0 && def->_index < (int)_collectors.size());
 
-  // Make sure we have enough slots allocated.
-  while ((int)_collectors.size() <= def->_index) {
-    _collectors.push_back(NULL);
+  if (_collectors[def->_index]._def != (PStatCollectorDef *)NULL) {
+    // Free the old definition, if any.
+    delete _collectors[def->_index]._def;
   }
 
-  if (_collectors[def->_index] != (PStatCollectorDef *)NULL) {
-    // Free any old definition.
-    delete _collectors[def->_index];
-  }
-
-  _collectors[def->_index] = def;
+  _collectors[def->_index]._def = def;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -288,3 +311,22 @@ record_new_frame(int thread_index, int frame_number,
   nassertv(thread_index >= 0 && thread_index < (int)_threads.size());
   _threads[thread_index]._data->record_new_frame(frame_number, frame_data);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatClientData::slot_collector
+//       Access: Private
+//  Description: Makes sure there is an entry in the array for a
+//               collector with the given index number.
+////////////////////////////////////////////////////////////////////
+void PStatClientData::
+slot_collector(int collector_index) {
+  // A sanity check on the index number.
+  nassertv(collector_index < 1000);
+
+  while ((int)_collectors.size() <= collector_index) {
+    Collector collector;
+    collector._def = (PStatCollectorDef *)NULL;
+    collector._is_level = false;
+    _collectors.push_back(collector);
+  }
+}

+ 12 - 1
pandatool/src/pstatserver/pStatClientData.h

@@ -36,6 +36,8 @@ public:
   const PStatCollectorDef &get_collector_def(int index) const;
   string get_collector_name(int index) const;
   string get_collector_fullname(int index) const;
+  void set_collector_has_level(int index, bool flag);
+  bool get_collector_has_level(int index) const;
 
   int get_num_threads() const;
   bool has_thread(int index) const;
@@ -50,12 +52,21 @@ public:
 
   void record_new_frame(int thread_index, int frame_number,
 			PStatFrameData *frame_data);
+private:
+  void slot_collector(int collector_index);
+
 
 private:
   bool _is_alive;
   PStatReader *_reader;
 
-  typedef vector<PStatCollectorDef *> Collectors;
+  class Collector {
+  public:
+    PStatCollectorDef *_def;
+    bool _is_level;
+  };
+
+  typedef vector<Collector> Collectors;
   Collectors _collectors;
 
   class Thread {

+ 26 - 2
pandatool/src/pstatserver/pStatGraph.I

@@ -67,7 +67,7 @@ get_label_color(int n) const {
 //               placement of guide bars.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatGraph::
-set_target_frame_rate(double frame_rate) {
+set_target_frame_rate(float frame_rate) {
   if (_target_frame_rate != frame_rate) {
     _target_frame_rate = frame_rate;
     normal_guide_bars();
@@ -80,7 +80,7 @@ set_target_frame_rate(double frame_rate) {
 //  Description: Returns the indicated target frame rate in Hz.  See
 //               set_target_frame_rate().
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatGraph::
+INLINE float PStatGraph::
 get_target_frame_rate() const {
   return _target_frame_rate;
 }
@@ -131,3 +131,27 @@ INLINE int PStatGraph::
 get_guide_bar_units() const {
   return _guide_bar_units;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatGraph::set_guide_bar_unit_name
+//       Access: Public
+//  Description: Sets the name of the units to be used for the guide
+//               bars if the units type is set to GBU_named |
+//               GBU_show_units.
+////////////////////////////////////////////////////////////////////
+INLINE void PStatGraph::
+set_guide_bar_unit_name(const string &unit_name) {
+  _unit_name = unit_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatGraph::get_guide_bar_unit_name
+//       Access: Public
+//  Description: Returns the name of the units to be used for the guide
+//               bars if the units type is set to GBU_named |
+//               GBU_show_units.
+////////////////////////////////////////////////////////////////////
+INLINE const string &PStatGraph::
+get_guide_bar_unit_name() const {
+  return _unit_name;
+}

+ 89 - 41
pandatool/src/pstatserver/pStatGraph.cxx

@@ -18,7 +18,7 @@
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 PStatGraph::GuideBar::
-GuideBar(double height, const string &label, bool is_target) :
+GuideBar(float height, const string &label, bool is_target) :
   _height(height),
   _label(label),
   _is_target(is_target)
@@ -98,6 +98,80 @@ get_guide_bar(int n) const {
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatGraph::format_number
+//       Access: Public, Static
+//  Description: Returns a string representing the value nicely
+//               formatted for its range.
+////////////////////////////////////////////////////////////////////
+string PStatGraph::
+format_number(float value) {
+  char buffer[128];
+
+  if (value < 0.01) {
+    sprintf(buffer, "%0.4f", value);
+  } else if (value < 0.1) {
+    sprintf(buffer, "%0.3f", value);
+  } else if (value < 1.0) {
+    sprintf(buffer, "%0.2f", value);
+  } else if (value < 10.0) {
+    sprintf(buffer, "%0.1f", value);
+  } else {
+    sprintf(buffer, "%0.0f", value);
+  }
+
+  return buffer;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatGraph::format_number
+//       Access: Public, Static
+//  Description: Returns a string representing the value nicely
+//               formatted for its range, including the units
+//               as indicated.
+////////////////////////////////////////////////////////////////////
+string PStatGraph::
+format_number(float value, int guide_bar_units, const string &unit_name) {
+  string label;
+
+  if ((guide_bar_units & GBU_named) != 0) {
+    // Units are whatever is specified by unit_name, not a time unit
+    // at all.
+    label = format_number(value);
+    if ((guide_bar_units & GBU_show_units) != 0 && !unit_name.empty()) {
+      label += " ";
+      label += unit_name;
+    }
+
+  } else {
+    // Units are either milliseconds or hz, or both.
+    if ((guide_bar_units & GBU_ms) != 0) {
+      float ms = value * 1000.0;
+      label += format_number(ms);
+      if ((guide_bar_units & GBU_show_units) != 0) {
+        label += " ms";
+      }
+    }
+    
+    if ((guide_bar_units & GBU_hz) != 0) {
+      float hz = 1.0 / value;
+
+      if ((guide_bar_units & GBU_ms) != 0) {
+        label += " (";
+      }
+      label += format_number(hz);
+      if ((guide_bar_units & GBU_show_units) != 0) {
+        label += " Hz";
+      }
+      if ((guide_bar_units & GBU_ms) != 0) {
+        label += ")";
+      }
+    }
+  }
+
+  return label;
+}
+
 // STL function object for sorting labels in order by the collector's
 // sort index, used in update_labels(), below.
 class SortCollectorLabels {
@@ -122,7 +196,7 @@ public:
 //  Description: Resets the list of guide bars.
 ////////////////////////////////////////////////////////////////////
 void PStatGraph::
-update_guide_bars(int num_bars, double scale) {
+update_guide_bars(int num_bars, float scale) {
   _guide_bars.clear();
 
   // We'd like to draw about num_bars bars on the chart.  But we also
@@ -133,9 +207,9 @@ update_guide_bars(int num_bars, double scale) {
   // Choose a suitable harmonic of the target frame rate near the
   // bottom part of the chart.
 
-  double bottom = (double)num_bars / scale;
+  float bottom = (float)num_bars / scale;
 
-  double harmonic;
+  float harmonic;
   if (_target_frame_rate < bottom) {
     // n * tfr
     harmonic = floor(bottom / _target_frame_rate + 0.5) * _target_frame_rate;
@@ -153,50 +227,24 @@ update_guide_bars(int num_bars, double scale) {
   _guide_bars_changed = true;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatGraph::make_guide_bar
 //       Access: Protected
-//  Description: Makes a guide bar for the indicated frame rate.
+//  Description: Makes a guide bar for the indicated elapsed time or
+//               level units.
 ////////////////////////////////////////////////////////////////////
 PStatGraph::GuideBar PStatGraph::
-make_guide_bar(double time) const {
-  string label;
-
-  char buffer[128];
+make_guide_bar(float value) const {
+  string label = format_number(value, _guide_bar_units, _unit_name);
 
-  if ((_guide_bar_units & GBU_ms) != 0) {
-    double ms = time * 1000.0;
-    if (ms < 10.0) {
-      sprintf(buffer, "%0.1f", ms);
-    } else {
-      sprintf(buffer, "%0.0f", ms);
-    }
-    label += buffer;
-    if ((_guide_bar_units & GBU_show_units) != 0) {
-      label += " ms";
-    }
-  }
+  bool is_target = false;
 
-  if ((_guide_bar_units & GBU_hz) != 0) {
-    double frame_rate = 1.0 / time;
-    if (frame_rate < 10.0) {
-      sprintf(buffer, "%0.1f", frame_rate);
-    } else {
-      sprintf(buffer, "%0.0f", frame_rate);
-    }
-    if ((_guide_bar_units & GBU_ms) != 0) {
-      label += " (";
-    }
-    label += buffer;
-    if ((_guide_bar_units & GBU_show_units) != 0) {
-      label += " Hz";
-    }
-    if ((_guide_bar_units & GBU_ms) != 0) {
-      label += ")";
-    }
+  if ((_guide_bar_units & GBU_named) == 0) {
+    // If it's a time unit, check to see if it matches our target
+    // frame rate.
+    float hz = 1.0 / value;
+    is_target = IS_NEARLY_EQUAL(hz, _target_frame_rate);
   }
 
-  return GuideBar(time, label,
-		  IS_NEARLY_EQUAL(1.0 / time, _target_frame_rate));
+  return GuideBar(value, label, is_target);
 }

+ 16 - 8
pandatool/src/pstatserver/pStatGraph.h

@@ -36,18 +36,18 @@ public:
   INLINE string get_label_name(int n) const;
   INLINE RGBColorf get_label_color(int n) const;
 
-  INLINE void set_target_frame_rate(double frame_rate);
-  INLINE double get_target_frame_rate() const;
+  INLINE void set_target_frame_rate(float frame_rate);
+  INLINE float get_target_frame_rate() const;
 
   INLINE int get_xsize() const;
   INLINE int get_ysize() const;
 
   class GuideBar {
   public:
-    GuideBar(double height, const string &label, bool is_target);
+    GuideBar(float height, const string &label, bool is_target);
     GuideBar(const GuideBar &copy);
 
-    double _height;
+    float _height;
     string _label;
     bool _is_target;
   };
@@ -55,7 +55,8 @@ public:
   enum GuideBarUnits {
     GBU_hz         = 0x0001,
     GBU_ms         = 0x0002,
-    GBU_show_units = 0x0004,
+    GBU_named      = 0x0004,
+    GBU_show_units = 0x0008,
   };
 
   int get_num_guide_bars() const;
@@ -63,18 +64,24 @@ public:
 
   INLINE void set_guide_bar_units(int unit_mask);
   INLINE int get_guide_bar_units() const;
+  INLINE void set_guide_bar_unit_name(const string &unit_name);
+  INLINE const string &get_guide_bar_unit_name() const;
+
+  static string format_number(float value);
+  static string format_number(float value, int guide_bar_units, 
+                              const string &unit_name = string());
 
 protected:
   virtual void normal_guide_bars()=0;
-  void update_guide_bars(int num_bars, double scale);
-  GuideBar make_guide_bar(double time) const;
+  void update_guide_bars(int num_bars, float scale);
+  GuideBar make_guide_bar(float value) const;
 
   bool _labels_changed;
   bool _guide_bars_changed;
 
   PT(PStatMonitor) _monitor;
 
-  double _target_frame_rate;
+  float _target_frame_rate;
 
   int _xsize;
   int _ysize;
@@ -87,6 +94,7 @@ protected:
   typedef vector<GuideBar> GuideBars;
   GuideBars _guide_bars;
   int _guide_bar_units;
+  string _unit_name;
 };
 
 #include "pStatGraph.I"

+ 30 - 3
pandatool/src/pstatserver/pStatMonitor.cxx

@@ -112,9 +112,9 @@ get_collector_color(int collector_index) {
 
   // We didn't have a color for the collector; make one up.
   RGBColorf random_color;
-  random_color[0] = (double)rand() / (double)RAND_MAX;
-  random_color[1] = (double)rand() / (double)RAND_MAX;
-  random_color[2] = (double)rand() / (double)RAND_MAX;
+  random_color[0] = (float)rand() / (float)RAND_MAX;
+  random_color[1] = (float)rand() / (float)RAND_MAX;
+  random_color[2] = (float)rand() / (float)RAND_MAX;
 
   ci = _colors.insert(Colors::value_type(collector_index, random_color)).first;
   return (*ci).second;
@@ -139,6 +139,33 @@ get_view(int thread_index) {
   return (*vi).second;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatMonitor::get_level_view
+//       Access: Public
+//  Description: Returns a view on the level value (as opposed to
+//               elapsed time) for the given collector over the given
+//               thread.  If there is no such view already for the
+//               indicated thread, this will create one.
+////////////////////////////////////////////////////////////////////
+PStatView &PStatMonitor::
+get_level_view(int collector_index, int thread_index) {
+  LevelViews::iterator lvi;
+  lvi = _level_views.find(collector_index);
+  if (lvi == _level_views.end()) {
+    lvi = _level_views.insert(LevelViews::value_type(collector_index, Views())).first;
+  }
+  Views &views = (*lvi).second;
+  
+  Views::iterator vi;
+  vi = views.find(thread_index);
+  if (vi == views.end()) {
+    vi = views.insert(Views::value_type(thread_index, PStatView())).first;
+    (*vi).second.set_thread_data(_client_data->get_thread_data(thread_index));
+    (*vi).second.constrain(collector_index, true);
+  }
+  return (*vi).second;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatMonitor::initialized
 //       Access: Public, Virtual

+ 3 - 0
pandatool/src/pstatserver/pStatMonitor.h

@@ -55,6 +55,7 @@ public:
   INLINE string get_client_progname() const;
 
   PStatView &get_view(int thread_index);
+  PStatView &get_level_view(int collector_index, int thread_index);
 
 
   // The following virtual methods may be overridden by a derived
@@ -84,6 +85,8 @@ private:
 
   typedef map<int, PStatView> Views;
   Views _views;
+  typedef map<int, Views> LevelViews;
+  LevelViews _level_views;
 
   typedef map<int, RGBColorf> Colors;
   Colors _colors;

+ 6 - 6
pandatool/src/pstatserver/pStatPianoRoll.I

@@ -10,7 +10,7 @@
 //               horizontal axis represents.  This may force a redraw.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatPianoRoll::
-set_horizontal_scale(double time_width) {
+set_horizontal_scale(float time_width) {
   if (_time_width != time_width) {
     _time_width = time_width;
     normal_guide_bars();
@@ -24,7 +24,7 @@ set_horizontal_scale(double time_width) {
 //  Description: Returns the amount of total time the width of the
 //               horizontal axis represents.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatPianoRoll::
+INLINE float PStatPianoRoll::
 get_horizontal_scale() const {
   return _time_width;
 }
@@ -35,8 +35,8 @@ get_horizontal_scale() const {
 //  Description: Converts a timestamp to a horizontal pixel offset.
 ////////////////////////////////////////////////////////////////////
 INLINE int PStatPianoRoll::
-timestamp_to_pixel(double time) const {
-  return (int)((double)_xsize * (time - _start_time) / _time_width);
+timestamp_to_pixel(float time) const {
+  return (int)((float)_xsize * (time - _start_time) / _time_width);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -44,7 +44,7 @@ timestamp_to_pixel(double time) const {
 //       Access: Public
 //  Description: Converts a horizontal pixel offset to a timestamp.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatPianoRoll::
+INLINE float PStatPianoRoll::
 pixel_to_timestamp(int x) const {
-  return _time_width * (double)x / (double)_xsize + _start_time;
+  return _time_width * (float)x / (float)_xsize + _start_time;
 }

+ 6 - 6
pandatool/src/pstatserver/pStatPianoRoll.cxx

@@ -42,7 +42,7 @@ clear() {
 //               second data point turns it off (ends the bar).
 ////////////////////////////////////////////////////////////////////
 void PStatPianoRoll::BarBuilder::
-add_data_point(double time) {
+add_data_point(float time) {
   if (_color_bars.empty() || _color_bars.back()._end >= 0.0) {
     // This is an odd-numbered data point: start the bar.
     ColorBar bar;
@@ -63,7 +63,7 @@ add_data_point(double time) {
 //               by a corresponding end-bar data point.
 ////////////////////////////////////////////////////////////////////
 void PStatPianoRoll::BarBuilder::
-finish(double time) {
+finish(float time) {
   if (!_color_bars.empty() && _color_bars.back()._end < 0.0) {
     nout << "Warning: collector was left on at the end of the frame.\n";
     _color_bars.back()._end = time;
@@ -262,7 +262,7 @@ public:
     // -1 appear to be a very large positive integer, thus placing
     // collectors with a -1 sort value at the very end.
     return 
-      (unsigned int)_client_data->get_collector_def(a)._sort <
+      (unsigned int)_client_data->get_collector_def(a)._sort >
       (unsigned int)_client_data->get_collector_def(b)._sort;
   }
   const PStatClientData *_client_data;
@@ -287,8 +287,8 @@ compute_page(const PStatFrameData &frame_data) {
 
   int num_events = frame_data.get_num_events();
   for (int i = 0; i < num_events; i++) {
-    int collector_index = (frame_data.get_collector(i) & 0x7fff);
-    double time = frame_data.get_time(i);
+    int collector_index = frame_data.get_time_collector(i);
+    float time = frame_data.get_time(i);
     _page_data[collector_index].add_data_point(time);
   }
 
@@ -312,7 +312,7 @@ compute_page(const PStatFrameData &frame_data) {
   }
 
   // Finally, make sure all of the bars are closed.
-  double time = frame_data.get_end();
+  float time = frame_data.get_end();
   for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) {
     (*pi).second.finish(time);
   }

+ 10 - 10
pandatool/src/pstatserver/pStatPianoRoll.h

@@ -39,11 +39,11 @@ public:
 
   void update();
 
-  INLINE void set_horizontal_scale(double time_width);
-  INLINE double get_horizontal_scale() const;
+  INLINE void set_horizontal_scale(float time_width);
+  INLINE float get_horizontal_scale() const;
 
-  INLINE int timestamp_to_pixel(double time) const;
-  INLINE double pixel_to_timestamp(int x) const;
+  INLINE int timestamp_to_pixel(float time) const;
+  INLINE float pixel_to_timestamp(int x) const;
 
 protected:
   void changed_size(int xsize, int ysize);
@@ -62,13 +62,13 @@ private:
 
   int _thread_index;
 
-  double _time_width;
-  double _start_time;
+  float _time_width;
+  float _start_time;
 
   class ColorBar {
   public:
-    double _start;
-    double _end;
+    float _start;
+    float _end;
   };
   typedef vector<ColorBar> ColorBars;
 
@@ -76,8 +76,8 @@ private:
   public:
     BarBuilder();
     void clear();
-    void add_data_point(double time);
-    void finish(double time);
+    void add_data_point(float time);
+    void finish(float time);
 
     bool _is_new;
     ColorBars _color_bars;

+ 12 - 0
pandatool/src/pstatserver/pStatReader.cxx

@@ -225,6 +225,18 @@ handle_client_udp_data(const Datagram &datagram) {
   PStatFrameData *frame_data = new PStatFrameData;
   frame_data->read_datagram(source);
 
+  // Check to see if any new collectors have level data.
+  int num_levels = frame_data->get_num_levels();
+  for (int i = 0; i < num_levels; i++) {
+    int collector_index = frame_data->get_level_collector(i);
+    if (!_client_data->get_collector_has_level(collector_index)) {
+      // This collector is now reporting level data, and it wasn't
+      // before.
+      _client_data->set_collector_has_level(collector_index, true);
+      _monitor->new_collector(collector_index);
+    }
+  }
+
   _client_data->record_new_frame(thread_index, frame_number, frame_data);
   _monitor->new_data(thread_index, frame_number);
 }

+ 23 - 34
pandatool/src/pstatserver/pStatStripChart.I

@@ -32,7 +32,7 @@ get_collector_index() const {
 //               horizontal axis represents.  This may force a redraw.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatStripChart::
-set_horizontal_scale(double time_width) {
+set_horizontal_scale(float time_width) {
   if (_time_width != time_width) {
     if (_scroll_mode) {
       _start_time += _time_width - time_width;
@@ -49,7 +49,7 @@ set_horizontal_scale(double time_width) {
 //  Description: Returns the amount of total time the width of the
 //               horizontal axis represents.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatStripChart::
+INLINE float PStatStripChart::
 get_horizontal_scale() const {
   return _time_width;
 }
@@ -57,38 +57,27 @@ get_horizontal_scale() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatStripChart::set_vertical_scale
 //       Access: Public
-//  Description: Changes the amount of time the height of the
-//               vertical axis represents.  This may force a redraw.
+//  Description: Changes the value the height of the vertical axis
+//               represents.  This may force a redraw.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatStripChart::
-set_vertical_scale(double time_height) {
-  if (_time_height != time_height) {
-    _time_height = time_height;
+set_vertical_scale(float value_height) {
+  if (_value_height != value_height) {
+    _value_height = value_height;
     normal_guide_bars();
     force_redraw();
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PStatStripChart::set_default_vertical_scale
-//       Access: Public
-//  Description: Sets the vertical scale to center the target frame
-//               rate bar.
-////////////////////////////////////////////////////////////////////
-INLINE void PStatStripChart::
-set_default_vertical_scale() {
-  set_vertical_scale(2.0 / get_target_frame_rate());
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatStripChart::get_vertical_scale
 //       Access: Public
-//  Description: Returns the amount of total time the height of the
-//               vertical axis represents.
+//  Description: Returns total value the height of the vertical axis
+//               represents.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatStripChart::
+INLINE float PStatStripChart::
 get_vertical_scale() const {
-  return _time_height;
+  return _value_height;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -127,8 +116,8 @@ get_scroll_mode() const {
 //  Description: Converts a timestamp to a horizontal pixel offset.
 ////////////////////////////////////////////////////////////////////
 INLINE int PStatStripChart::
-timestamp_to_pixel(double time) const {
-  return (int)((double)get_xsize() * (time - _start_time) / _time_width);
+timestamp_to_pixel(float time) const {
+  return (int)((float)get_xsize() * (time - _start_time) / _time_width);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -136,29 +125,29 @@ timestamp_to_pixel(double time) const {
 //       Access: Public
 //  Description: Converts a horizontal pixel offset to a timestamp.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatStripChart::
+INLINE float PStatStripChart::
 pixel_to_timestamp(int x) const {
-  return _time_width * (double)x / (double)get_xsize() + _start_time;
+  return _time_width * (float)x / (float)get_xsize() + _start_time;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatStripChart::height_to_pixel
 //       Access: Public
-//  Description: Converts an elapsed time (e.g. a "height" in the
-//               strip chart) to a vertical pixel offset.
+//  Description: Converts a value (i.e. a "height" in the strip chart)
+//               to a vertical pixel offset.
 ////////////////////////////////////////////////////////////////////
 INLINE int PStatStripChart::
-height_to_pixel(double elapsed_time) const {
-  return get_ysize() - (int)((double)get_ysize() * elapsed_time / _time_height);
+height_to_pixel(float value) const {
+  return get_ysize() - (int)((float)get_ysize() * value / _value_height);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatStripChart::pixel_to_height
 //       Access: Public
-//  Description: Converts a vertical pixel offset to an elapsed time
-//               (a "height" in the strip chart).
+//  Description: Converts a vertical pixel offset to a value (a
+//               "height" in the strip chart).
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatStripChart::
+INLINE float PStatStripChart::
 pixel_to_height(int x) const {
-  return _time_height * (double)(get_ysize() - x) / (double)get_ysize();
+  return _value_height * (float)(get_ysize() - x) / (float)get_ysize();
 }

+ 88 - 18
pandatool/src/pstatserver/pStatStripChart.cxx

@@ -4,13 +4,14 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "pStatStripChart.h"
+#include "pStatClientData.h"
+#include "pStatMonitor.h"
 
 #include <pStatFrameData.h>
 #include <pStatCollectorDef.h>
 #include <string_utils.h>
 #include <config_pstats.h>
 
-#include <stdio.h>  // for sprintf
 #include <algorithm>
 
 ////////////////////////////////////////////////////////////////////
@@ -32,11 +33,20 @@ PStatStripChart(PStatMonitor *monitor, PStatView &view,
   _cursor_pixel = 0;
 
   _time_width = 20.0;
-  _time_height = 1.0/10.0;
+  _value_height = 1.0/10.0;
   _start_time = 0.0;
 
   _level_index = 0;
 
+  const PStatClientData *client_data = _monitor->get_client_data();
+  if (client_data->has_collector(_collector_index)) {
+    const PStatCollectorDef &def = client_data->get_collector_def(_collector_index);
+    _unit_name = def._level_units;
+    if (!_unit_name.empty()) {
+      _guide_bar_units = GBU_named;
+    }
+  }
+
   set_default_vertical_scale();
 }
 
@@ -85,7 +95,7 @@ update() {
       _next_frame = latest;
       
       // Clean out the old data.
-      double oldest_time = 
+      float oldest_time = 
 	thread_data->get_frame(latest).get_start() - _time_width;
       
       Data::iterator di;
@@ -116,6 +126,66 @@ first_data() const {
   return _first_data;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PStatStripChart::set_default_vertical_scale
+//       Access: Public
+//  Description: Sets the vertical scale according to the suggested
+//               scale of the base collector, if any, or to center the
+//               target frame rate bar otherwise.
+////////////////////////////////////////////////////////////////////
+void PStatStripChart::
+set_default_vertical_scale() {
+  const PStatClientData *client_data = _monitor->get_client_data();
+  if (client_data->has_collector(_collector_index)) {
+    const PStatCollectorDef &def = 
+      client_data->get_collector_def(_collector_index);
+    if (def._suggested_scale != 0.0) {
+      set_vertical_scale(def._suggested_scale);
+      return;
+    }
+  }
+
+  set_vertical_scale(2.0 / get_target_frame_rate());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatStripChart::set_auto_vertical_scale
+//       Access: Public
+//  Description: Sets the vertical scale to make all the data visible.
+////////////////////////////////////////////////////////////////////
+void PStatStripChart::
+set_auto_vertical_scale() {
+  const PStatThreadData *thread_data = _view.get_thread_data();
+
+  float max_value = 0.0;
+
+  for (int x = 0; x <= _xsize; x++) {
+    float time = pixel_to_timestamp(x);
+    int frame_number = 
+      thread_data->get_frame_number_at_time(time, frame_number);
+
+    if (thread_data->has_frame(frame_number)) {
+      const FrameData &frame = get_frame_data(frame_number);
+      
+      float overall_value = 0.0;
+      FrameData::const_iterator fi;
+      for (fi = frame.begin(); fi != frame.end(); ++fi) {
+        const ColorData &cd = (*fi);
+        overall_value += cd._net_value;
+      }
+      max_value = max(max_value, overall_value);
+    }
+  }
+
+  // Ok, now we know what the max value visible in the chart is.
+  // Choose a scale that will show all of this sensibly.
+  if (max_value == 0.0) {
+    set_vertical_scale(1.0);
+  } else {
+    set_vertical_scale(max_value * 1.1);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatStripChart::get_collector_under_pixel
 //       Access: Public
@@ -127,23 +197,23 @@ int PStatStripChart::
 get_collector_under_pixel(int xpoint, int ypoint) {
   // First, we need to know what frame it was; to know that, we need
   // to determine the time corresponding to the x pixel.
-  double time = pixel_to_timestamp(xpoint);
+  float time = pixel_to_timestamp(xpoint);
   
   // Now use that time to determine the frame.
   const PStatThreadData *thread_data = _view.get_thread_data();
   int frame_number = thread_data->get_frame_number_at_time(time);
 
   // And now we can determine which collector within the frame,
-  // based on the time height.
+  // based on the value height.
   const FrameData &frame = get_frame_data(frame_number);
-  double overall_time = 0.0;
+  float overall_value = 0.0;
   int y = get_ysize();
 
   FrameData::const_iterator fi;
   for (fi = frame.begin(); fi != frame.end(); ++fi) {
     const ColorData &cd = (*fi);
-    overall_time += cd._net_time;
-    y = height_to_pixel(overall_time);
+    overall_value += cd._net_value;
+    y = height_to_pixel(overall_value);
     if (y <= ypoint) {
       return cd._collector_index;
     }
@@ -179,18 +249,18 @@ get_frame_data(int frame_number) {
     const PStatViewLevel *child = level->get_child(i);
     ColorData cd;
     cd._collector_index = child->get_collector();
-    cd._net_time = child->get_net_time();
-    if (cd._net_time != 0.0) {
+    cd._net_value = child->get_net_value();
+    if (cd._net_value != 0.0) {
       data.push_back(cd);
     }
   }
 
-  // Also, there might be some time in the overall Collector that
+  // Also, there might be some value in the overall Collector that
   // wasn't included in all of the children.
   ColorData cd;
   cd._collector_index = level->get_collector();
-  cd._net_time = level->get_time_alone();
-  if (cd._net_time != 0.0) {
+  cd._net_value = level->get_value_alone();
+  if (cd._net_value != 0.0) {
     data.push_back(cd);
   }
 
@@ -218,7 +288,7 @@ changed_size(int xsize, int ysize) {
 	
       } else {
 	// Redraw the stats that were there before.
-	double old_start_time = _start_time;
+	float old_start_time = _start_time;
 	
 	// Back up a bit to draw the stuff to the right of the cursor.
 	_start_time -= _time_width;
@@ -366,7 +436,7 @@ public:
     // -1 appear to be a very large positive integer, thus placing
     // collectors with a -1 sort value at the very end.
     return 
-      (unsigned int)_client_data->get_collector_def(a)._sort <
+      (unsigned int)_client_data->get_collector_def(a)._sort >
       (unsigned int)_client_data->get_collector_def(b)._sort;
   }
   const PStatClientData *_client_data;
@@ -407,7 +477,7 @@ update_labels() {
 ////////////////////////////////////////////////////////////////////
 void PStatStripChart::
 normal_guide_bars() {
-  update_guide_bars(4, _time_height);
+  update_guide_bars(4, _value_height);
 }
 
 
@@ -468,7 +538,7 @@ draw_frames(int first_frame, int last_frame) {
       copy_region(slide_pixels, first_pixel, 0);
       first_pixel -= slide_pixels;
       last_pixel -= slide_pixels;
-      _start_time += (double)slide_pixels / (double)_xsize * _time_width;
+      _start_time += (float)slide_pixels / (float)_xsize * _time_width;
       draw_pixels(first_pixel, last_pixel);
 
     } else {
@@ -499,7 +569,7 @@ draw_pixels(int first_pixel, int last_pixel) {
       draw_cursor(x);
 
     } else {
-      double time = pixel_to_timestamp(x);
+      float time = pixel_to_timestamp(x);
       frame_number = thread_data->get_frame_number_at_time(time, frame_number);
       
       if (thread_data->has_frame(frame_number)) {

+ 16 - 16
pandatool/src/pstatserver/pStatStripChart.h

@@ -23,9 +23,8 @@ class PStatView;
 // 	 Class : PStatStripChart
 // Description : This is an abstract class that presents the interface
 //               for drawing a basic strip-chart, showing the relative
-//               time elapsed over an interval of time for several
-//               different collectors, differentiated by bands of
-//               color.
+//               value over an interval of time for several different
+//               collectors, differentiated by bands of color.
 //
 //               This class just manages all the strip-chart logic;
 //               the actual nuts and bolts of drawing pixels is left
@@ -44,26 +43,27 @@ public:
   INLINE PStatView &get_view() const;
   INLINE int get_collector_index() const;
 
-  INLINE void set_horizontal_scale(double time_width);
-  INLINE double get_horizontal_scale() const;
-  INLINE void set_vertical_scale(double time_height);
-  INLINE void set_default_vertical_scale();
-  INLINE double get_vertical_scale() const;
+  INLINE void set_horizontal_scale(float time_width);
+  INLINE float get_horizontal_scale() const;
+  INLINE void set_vertical_scale(float value_height);
+  void set_default_vertical_scale();
+  void set_auto_vertical_scale();
+  INLINE float get_vertical_scale() const;
 
   INLINE void set_scroll_mode(bool scroll_mode);
   INLINE bool get_scroll_mode() const;
 
   int get_collector_under_pixel(int xpoint, int ypoint);
-  INLINE int timestamp_to_pixel(double time) const;
-  INLINE double pixel_to_timestamp(int x) const;
-  INLINE int height_to_pixel(double elapsed_time) const;
-  INLINE double pixel_to_height(int y) const;
+  INLINE int timestamp_to_pixel(float time) const;
+  INLINE float pixel_to_timestamp(int x) const;
+  INLINE int height_to_pixel(float value) const;
+  INLINE float pixel_to_height(int y) const;
 
 protected:
   class ColorData {
   public:
     int _collector_index;
-    double _net_time;
+    float _net_value;
   };
   typedef vector<ColorData> FrameData;
   typedef map<int, FrameData> Data;
@@ -101,9 +101,9 @@ private:
 
   int _level_index;
 
-  double _time_width;
-  double _start_time;
-  double _time_height;
+  float _time_width;
+  float _start_time;
+  float _value_height;
 };
 
 #include "pStatStripChart.I"

+ 13 - 13
pandatool/src/pstatserver/pStatThreadData.cxx

@@ -122,7 +122,7 @@ get_frame(int frame_number) const {
 //  Description: Returns the timestamp (in seconds elapsed since
 //               connection) of the latest available frame.
 ////////////////////////////////////////////////////////////////////
-double PStatThreadData::
+float PStatThreadData::
 get_latest_time() const {
   nassertr(!_frames.empty(), 0.0);
   return _frames.back()->get_start();
@@ -134,7 +134,7 @@ get_latest_time() const {
 //  Description: Returns the timestamp (in seconds elapsed since
 //               connection) of the oldest available frame.
 ////////////////////////////////////////////////////////////////////
-double PStatThreadData::
+float PStatThreadData::
 get_oldest_time() const {
   nassertr(!_frames.empty(), 0.0);
   return _frames.front()->get_start();
@@ -147,7 +147,7 @@ get_oldest_time() const {
 //               latest frame not later than the indicated time.
 ////////////////////////////////////////////////////////////////////
 const PStatFrameData &PStatThreadData::
-get_frame_at_time(double time) const {
+get_frame_at_time(float time) const {
   return get_frame(get_frame_number_at_time(time));
 }
 
@@ -162,7 +162,7 @@ get_frame_at_time(double time) const {
 //               which may speed the search for the frame.
 ////////////////////////////////////////////////////////////////////
 int PStatThreadData::
-get_frame_number_at_time(double time, int hint) const {
+get_frame_number_at_time(float time, int hint) const {
   hint -= _first_frame_number;
   if (hint >= 0 && hint < (int)_frames.size()) {
     if (_frames[hint] != (PStatFrameData *)NULL &&
@@ -211,8 +211,8 @@ get_latest_frame() const {
 //               of seconds, by counting up the number of frames
 //               elapsed in that time interval.
 ////////////////////////////////////////////////////////////////////
-double PStatThreadData::
-get_frame_rate(double time) const {
+float PStatThreadData::
+get_frame_rate(float time) const {
   if (_frames.empty()) {
     // No frames in the data at all; nothing to base the frame rate
     // on.
@@ -229,8 +229,8 @@ get_frame_rate(double time) const {
   }
   nassertr(_frames[now_i] != (PStatFrameData *)NULL, 0.0);
 
-  double now = _frames[now_i]->get_end();
-  double then = now - time;
+  float now = _frames[now_i]->get_end();
+  float then = now - time;
 
   int then_i = now_i;
   int last_good_i = now_i;
@@ -246,7 +246,7 @@ get_frame_rate(double time) const {
   nassertr(_frames[last_good_i] != (PStatFrameData *)NULL, 0.0);
 
   int num_frames = now_i - last_good_i + 1;
-  return (double)num_frames / (now - _frames[last_good_i]->get_start());
+  return (float)num_frames / (now - _frames[last_good_i]->get_start());
 }
 
 
@@ -259,7 +259,7 @@ get_frame_rate(double time) const {
 //               frame that may be queried is.
 ////////////////////////////////////////////////////////////////////
 void PStatThreadData::
-set_history(double time) {
+set_history(float time) {
   _history = time;
 }
 
@@ -271,7 +271,7 @@ set_history(double time) {
 //               new frame is added.  This affects how old the oldest
 //               frame that may be queried is.
 ////////////////////////////////////////////////////////////////////
-double PStatThreadData::
+float PStatThreadData::
 get_history() const {
   return _history;
 }
@@ -292,11 +292,11 @@ void PStatThreadData::
 record_new_frame(int frame_number, PStatFrameData *frame_data) {
   nassertv(frame_data != (PStatFrameData *)NULL);
   nassertv(!frame_data->is_empty());
-  double time = frame_data->get_start();
+  float time = frame_data->get_start();
 
   // First, remove all the old frames that fall outside of our
   // history window.
-  double oldest_allowable_time = time - _history;
+  float oldest_allowable_time = time - _history;
   while (!_frames.empty() && 
 	 (_frames.front() == (PStatFrameData *)NULL ||
 	  _frames.front()->is_empty() ||

+ 8 - 8
pandatool/src/pstatserver/pStatThreadData.h

@@ -40,18 +40,18 @@ public:
   bool has_frame(int frame_number) const;
   const PStatFrameData &get_frame(int frame_number) const;
 
-  double get_latest_time() const;
-  double get_oldest_time() const;
-  const PStatFrameData &get_frame_at_time(double time) const;
-  int get_frame_number_at_time(double time, int hint = -1) const;
+  float get_latest_time() const;
+  float get_oldest_time() const;
+  const PStatFrameData &get_frame_at_time(float time) const;
+  int get_frame_number_at_time(float time, int hint = -1) const;
 
   const PStatFrameData &get_latest_frame() const;
 
-  double get_frame_rate(double time = 3.0) const;
+  float get_frame_rate(float time = 3.0) const;
 
 
-  void set_history(double time);
-  double get_history() const;
+  void set_history(float time);
+  float get_history() const;
 
   void record_new_frame(int frame_number, PStatFrameData *frame_data);
 
@@ -61,7 +61,7 @@ private:
   typedef deque<PStatFrameData *> Frames;
   Frames _frames;
   int _first_frame_number;
-  double _history;
+  float _history;
 
   static PStatFrameData _null_frame;
 };

+ 1 - 1
pandatool/src/pstatserver/pStatView.I

@@ -52,7 +52,7 @@ set_to_frame(int frame_number) {
 //               set_to_frame.
 ////////////////////////////////////////////////////////////////////
 INLINE void PStatView::
-set_to_time(double time) {
+set_to_time(float time) {
   set_to_frame(_thread_data->get_frame_at_time(time));
 }
 

+ 242 - 99
pandatool/src/pstatserver/pStatView.cxx

@@ -18,8 +18,8 @@
 // 	 Class : FrameSample
 // Description : This class is used within this module only--in fact,
 //               within PStatView::set_to_frame() only--to help
-//               collect data out of the PStatFrameData object and
-//               boil it down to a list of elapsed times.
+//               collect event data out of the PStatFrameData object
+//               and boil it down to a list of elapsed times.
 ////////////////////////////////////////////////////////////////////
 class FrameSample {
 public: 
@@ -31,10 +31,25 @@ public:
     _pushed = false;
     _net_time = 0.0;
   }
-  void data_point(double time, Started &started) {
+  void data_point(float time, bool is_start, Started &started) {
     _touched = true;
-    _is_started = !_is_started;
 
+    // We only consider events that change the start/stop state.
+    // With two consecutive 'start' events, for instance, we ignore
+    // the second one.
+    
+    // *** That's not quite the right thing to do.  We should keep
+    // track of the nesting level and bracket things correctly, so
+    // that we ignore the second start and the *first* stop, but
+    // respect the outer start/stop.  For the short term, this
+    // works, because the client is already doing this logic and
+    // won't send us nested start/stop pairs, but we'd like to
+    // generalize this in the future so we can deal with these
+    // nested pairs properly.
+    nassertv(is_start != _is_started);
+
+    _is_started = is_start;
+      
     if (_pushed) {
       nassertv(!_is_started);
       Started::iterator si = find(started.begin(), started.end(), this);
@@ -43,19 +58,19 @@ public:
       
     } else {
       if (_is_started) {
-	_net_time -= time;
-	push_all(time, started);
-	started.push_back(this);
+        _net_time -= time;
+        push_all(time, started);
+        started.push_back(this);
       } else {
-	_net_time += time;
-	Started::iterator si = find(started.begin(), started.end(), this);
-	nassertv(si != started.end());
-	started.erase(si);
-	pop_one(time, started);
+        _net_time += time;
+        Started::iterator si = find(started.begin(), started.end(), this);
+        nassertv(si != started.end());
+        started.erase(si);
+        pop_one(time, started);
       }
     }
   }
-  void push(double time) {
+  void push(float time) {
     if (!_pushed) {
       _pushed = true;
       if (_is_started) {
@@ -63,7 +78,7 @@ public:
       }
     }
   }
-  void pop(double time) {
+  void pop(float time) {
     if (_pushed) {
       _pushed = false;
       if (_is_started) {
@@ -72,14 +87,14 @@ public:
     }
   }
 
-  void push_all(double time, Started &started) {
+  void push_all(float time, Started &started) {
     Started::iterator si;
     for (si = started.begin(); si != started.end(); ++si) {
       (*si)->push(time);
     }
   }
 
-  void pop_one(double time, Started &started) {
+  void pop_one(float time, Started &started) {
     Started::reverse_iterator si;
     for (si = started.rbegin(); si != started.rend(); ++si) {
       if ((*si)->_pushed) {
@@ -92,7 +107,7 @@ public:
   bool _touched;
   bool _is_started;
   bool _pushed;
-  double _net_time;
+  float _net_time;
 };
 
 
@@ -105,6 +120,7 @@ public:
 PStatView::
 PStatView() {
   _constraint = 0;
+  _show_level = false;
   _all_collectors_known = false;
   _level_index = 0;
 }
@@ -130,13 +146,20 @@ PStatView::
 //               reporting only the collector and its immediate
 //               parents.
 //
+//               When you constrain the view, you may also specify
+//               whether the view should show time data or level data
+//               for the indicated collector.  If level data, it
+//               reports the levels for the collector, and all of its
+//               children; otherwise, it collects the elapsed time.
+//
 //               Changing the constraint causes the current frame's
 //               data to become invalidated; you must then call
 //               set_to_frame() again to get any useful data out.
 ////////////////////////////////////////////////////////////////////
 void PStatView::
-constrain(int collector) {
+constrain(int collector, bool show_level) {
   _constraint = collector;
+  _show_level = show_level;
   clear_levels();
 }
 
@@ -148,7 +171,7 @@ constrain(int collector) {
 ////////////////////////////////////////////////////////////////////
 void PStatView::
 unconstrain() {
-  constrain(0);
+  constrain(0, false);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -184,6 +207,107 @@ set_to_frame(const PStatFrameData &frame_data) {
   nassertv(!_thread_data.is_null());
   nassertv(!_client_data.is_null());
 
+  if (_show_level) {
+    update_level_data(frame_data);
+  } else {
+    update_time_data(frame_data);
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::all_collectors_known
+//       Access: Public
+//  Description: After a call to set_to_frame(), this returns true if
+//               all collectors in the FrameData are known by the
+//               PStatsData object, or false if some are still unknown
+//               (even those that do not appear in the view).
+////////////////////////////////////////////////////////////////////
+bool PStatView::
+all_collectors_known() const {
+  return _all_collectors_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::get_net_value
+//       Access: Public
+//  Description: Returns the total value accounted for by the frame (or
+//               by whatever Collector we are constrained to).  This
+//               is the sum of all of the individual levels'
+//               get_net_value() value.
+////////////////////////////////////////////////////////////////////
+float PStatView::
+get_net_value() const {
+  float net = 0.0;
+  Levels::const_iterator li;
+  for (li = _levels.begin(); li != _levels.end(); ++li) {
+    net += (*li).second->_value_alone;
+  }
+
+  return net;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::get_top_level
+//       Access: Public
+//  Description: Returns a pointer to the level that corresponds to
+//               the Collector we've constrained to.  This is the top
+//               of a graph of levels; typically the next level
+//               down--the children of this level--will be the levels
+//               you want to display to the user.
+////////////////////////////////////////////////////////////////////
+const PStatViewLevel *PStatView::
+get_top_level() {
+  return get_level(_constraint);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::has_level
+//       Access: Public
+//  Description: Returns true if there is a level defined for the
+//               particular collector, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PStatView::
+has_level(int collector) const {
+  Levels::const_iterator li;
+  li = _levels.find(collector);
+  return (li != _levels.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::get_level
+//       Access: Public
+//  Description: Returns a pointer to the level that corresponds to
+//               the indicated Collector.  If there is no such level
+//               in the view, one will be created--use with caution.
+//               Check has_level() first if you don't want this
+//               behavior.
+////////////////////////////////////////////////////////////////////
+PStatViewLevel *PStatView::
+get_level(int collector) {
+  Levels::const_iterator li;
+  li = _levels.find(collector);
+  if (li != _levels.end()) {
+    return (*li).second;
+  }
+
+  PStatViewLevel *level = new PStatViewLevel;
+  level->_collector = collector;
+  level->_parent = NULL;
+  _levels[collector] = level;
+
+  reset_level(level);
+  return level;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PStatView::update_time_data
+//       Access: Private
+//  Description: The implementation of set_to_frame() for views that
+//               show elapsed time.
+////////////////////////////////////////////////////////////////////
+void PStatView::
+update_time_data(const PStatFrameData &frame_data) {
   int num_events = frame_data.get_num_events();
 
   typedef vector<FrameSample> Samples;
@@ -200,7 +324,8 @@ set_to_frame(const PStatFrameData &frame_data) {
 
   int i;
   for (i = 0; i < num_events; i++) {
-    int collector_index = (frame_data.get_collector(i) & 0x7fff);
+    int collector_index = frame_data.get_time_collector(i);
+    bool is_start = frame_data.is_start(i);
 
     if (!_client_data->has_collector(collector_index)) {
       _all_collectors_known = false;
@@ -211,7 +336,7 @@ set_to_frame(const PStatFrameData &frame_data) {
       if (_client_data->get_child_distance(_constraint, collector_index) >= 0) {
 	// Here's a data point we care about: anything at constraint
 	// level or below.
-	samples[collector_index].data_point(frame_data.get_time(i), started);
+	samples[collector_index].data_point(frame_data.get_time(i), is_start, started);
 	got_samples.insert(collector_index);
       }
     }
@@ -224,7 +349,7 @@ set_to_frame(const PStatFrameData &frame_data) {
     if ((*si)._is_started) {
       nout << _client_data->get_collector_fullname(i)
 	   << " was not stopped at frame end!\n";
-      (*si).data_point(frame_data.get_end(), started);
+      (*si).data_point(frame_data.get_end(), false, started);
     }
   }
 
@@ -251,7 +376,7 @@ set_to_frame(const PStatFrameData &frame_data) {
     GotSamples::iterator gi;
     gi = got_samples.find(collector_index);
     if (gi != got_samples.end()) {
-      level->_time_alone = samples[collector_index]._net_time;
+      level->_value_alone = samples[collector_index]._net_time;
       got_samples.erase(gi);
     }
 
@@ -267,7 +392,7 @@ set_to_frame(const PStatFrameData &frame_data) {
     for (gi = got_samples.begin(); gi != got_samples.end(); ++gi) {
       int collector_index = (*gi);
       PStatViewLevel *level = get_level(collector_index);
-      level->_time_alone = samples[*gi]._net_time;
+      level->_value_alone = samples[*gi]._net_time;
     }
   }
 
@@ -276,90 +401,108 @@ set_to_frame(const PStatFrameData &frame_data) {
   }
 }
 
-
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatView::all_collectors_known
-//       Access: Public
-//  Description: After a call to set_to_frame(), this returns true if
-//               all collectors in the FrameData are known by the
-//               PStatsData object, or false if some are still unknown
-//               (even those that do not appear in the view).
+//     Function: PStatView::update_level_data
+//       Access: Private
+//  Description: The implementation of set_to_frame() for views that
+//               show level values.
 ////////////////////////////////////////////////////////////////////
-bool PStatView::
-all_collectors_known() const {
-  return _all_collectors_known;
-}
+void PStatView::
+update_level_data(const PStatFrameData &frame_data) {
+  _all_collectors_known = true;
 
-////////////////////////////////////////////////////////////////////
-//     Function: PStatView::get_net_time
-//       Access: Public
-//  Description: Returns the total time accounted for by the frame (or
-//               by whatever Collector we are constrained to).  This
-//               is the sum of all of the individual levels'
-//               get_net_time() value.
-////////////////////////////////////////////////////////////////////
-double PStatView::
-get_net_time() const {
-  double net = 0.0;
-  Levels::const_iterator li;
-  for (li = _levels.begin(); li != _levels.end(); ++li) {
-    net += (*li).second->_time_alone;
+
+  // This tracks the set of level values we got.
+  typedef map<int, float> GotValues;
+  GotValues net_values;
+
+  int i;
+  int num_levels = frame_data.get_num_levels();
+  for (i = 0; i < num_levels; i++) {
+    int collector_index = frame_data.get_level_collector(i);
+    float value = frame_data.get_level(i);
+
+    if (!_client_data->has_collector(collector_index)) {
+      _all_collectors_known = false;
+
+    } else {
+      if (_client_data->get_child_distance(_constraint, collector_index) >= 0) {
+        net_values[collector_index] = value;
+      }
+    }
   }
 
-  return net;
-}
+  // Now that we've counted up the net level for each collector,
+  // compute the level for each collector alone by subtracting out
+  // each child from its parents.  If a parent has no data, nothing is
+  // subtracted.
+  GotValues alone_values = net_values;
+
+  GotValues::iterator gi;
+  for (gi = net_values.begin(); gi != net_values.end(); ++gi) {
+    int collector_index = (*gi).first;
+    float value = (*gi).second;
+
+    // Walk up to the top.
+    while (collector_index != 0 && collector_index != _constraint) {
+      const PStatCollectorDef &def =
+        _client_data->get_collector_def(collector_index);
+      int parent_index = def._parent_index;
+      GotValues::iterator pi = alone_values.find(parent_index);
+      if (pi != alone_values.end()) {
+        // The parent has data; subtract it.
+        (*pi).second -= value;
+      }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PStatView::get_top_level
-//       Access: Public
-//  Description: Returns a pointer to the level that corresponds to
-//               the Collector we've constrained to.  This is the top
-//               of a graph of levels; typically the next level
-//               down--the children of this level--will be the levels
-//               you want to display to the user.
-////////////////////////////////////////////////////////////////////
-const PStatViewLevel *PStatView::
-get_top_level() {
-  return get_level(_constraint);
-}
+      collector_index = parent_index;
+    }
+  }
+      
 
-////////////////////////////////////////////////////////////////////
-//     Function: PStatView::has_level
-//       Access: Public
-//  Description: Returns true if there is a level defined for the
-//               particular collector, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool PStatView::
-has_level(int collector) const {
-  Levels::const_iterator li;
-  li = _levels.find(collector);
-  return (li != _levels.end());
-}
+  bool any_new_levels = false;
 
-////////////////////////////////////////////////////////////////////
-//     Function: PStatView::get_level
-//       Access: Public
-//  Description: Returns a pointer to the level that corresponds to
-//               the indicated Collector.  If there is no such level
-//               in the view, one will be created--use with caution.
-//               Check has_level() first if you don't want this
-//               behavior.
-////////////////////////////////////////////////////////////////////
-PStatViewLevel *PStatView::
-get_level(int collector) {
-  Levels::const_iterator li;
-  li = _levels.find(collector);
-  if (li != _levels.end()) {
-    return (*li).second;
+  // Now match these samples we got up with those we already had in
+  // the levels.
+  Levels::iterator li, lnext;
+  li = _levels.begin();
+  while (li != _levels.end()) {
+    // Be careful while traversing a container and calling functions
+    // that could modify that container.
+    lnext = li;
+    ++lnext;
+
+    PStatViewLevel *level = (*li).second;
+    if (reset_level(level)) {
+      any_new_levels = true;
+    }
+
+    int collector_index = level->_collector;
+    GotValues::iterator gi;
+    gi = alone_values.find(collector_index);
+    if (gi != alone_values.end()) {
+      level->_value_alone = (*gi).second;
+      alone_values.erase(gi);
+    }
+
+    li = lnext;
   }
 
-  PStatViewLevel *level = new PStatViewLevel;
-  level->_collector = collector;
-  level->_parent = NULL;
-  _levels[collector] = level;
+  // Finally, any values left over in the alone_values set are new
+  // collectors that we need to add to the Levels list.
+  if (!alone_values.empty()) {
+    any_new_levels = true;
 
-  reset_level(level);
-  return level;
+    GotValues::const_iterator gi;
+    for (gi = alone_values.begin(); gi != alone_values.end(); ++gi) {
+      int collector_index = (*gi).first;
+      PStatViewLevel *level = get_level(collector_index);
+      level->_value_alone = (*gi).second;
+    }
+  }
+
+  if (any_new_levels) {
+    _level_index++;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -379,7 +522,7 @@ clear_levels() {
 ////////////////////////////////////////////////////////////////////
 //     Function: PStatView::reset_level
 //       Access: Private
-//  Description: Resets the total time of the Level to zero, and also
+//  Description: Resets the total value of the Level to zero, and also
 //               makes sure it is parented to the right Level
 //               corresponding to its Collector's parent.  Since the
 //               client might change its mind from time to time about
@@ -392,7 +535,7 @@ clear_levels() {
 bool PStatView::
 reset_level(PStatViewLevel *level) {
   bool any_changed = false;
-  level->_time_alone = 0.0;
+  level->_value_alone = 0.0;
 
   if (level->_collector == _constraint) {
     return false;

+ 7 - 3
pandatool/src/pstatserver/pStatView.h

@@ -27,7 +27,7 @@ public:
   PStatView();
   ~PStatView();
 
-  void constrain(int collector);
+  void constrain(int collector, bool show_level);
   void unconstrain();
 
   void set_thread_data(const PStatThreadData *thread_data);
@@ -36,10 +36,10 @@ public:
 
   void set_to_frame(const PStatFrameData &frame_data);
   INLINE void set_to_frame(int frame_number);
-  INLINE void set_to_time(double time);
+  INLINE void set_to_time(float time);
 
   bool all_collectors_known() const;
-  double get_net_time() const;
+  float get_net_value() const;
 
   const PStatViewLevel *get_top_level();
 
@@ -49,10 +49,14 @@ public:
   INLINE int get_level_index() const;
 
 private:
+  void update_time_data(const PStatFrameData &frame_data);
+  void update_level_data(const PStatFrameData &frame_data);
+
   void clear_levels();
   bool reset_level(PStatViewLevel *level);
 
   int _constraint;
+  bool _show_level;
   bool _all_collectors_known;
 
   typedef map<int, PStatViewLevel *> Levels;

+ 7 - 7
pandatool/src/pstatserver/pStatViewLevel.I

@@ -16,13 +16,13 @@ get_collector() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatViewLevel::get_time_alone
+//     Function: PStatViewLevel::get_value_alone
 //       Access: Public
-//  Description: Returns the total elapsed time spent by this
-//               Collector, not including any time spent in its child
-//               Collectors.
+//  Description: Returns the total level value (or elapsed time value)
+//               for this Collector, not including any values
+//               accounted for by its child Collectors.
 ////////////////////////////////////////////////////////////////////
-INLINE double PStatViewLevel::
-get_time_alone() const {
-  return _time_alone;
+INLINE float PStatViewLevel::
+get_value_alone() const {
+  return _value_alone;
 }

+ 11 - 11
pandatool/src/pstatserver/pStatViewLevel.cxx

@@ -12,19 +12,19 @@
 #include <algorithm>
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PStatViewLevel::get_net_time
+//     Function: PStatViewLevel::get_net_value
 //       Access: Public
-//  Description: Returns the total elapsed time spent by this
-//               Collector, including all time spent in its child
-//               Collectors.
+//  Description: Returns the total level value (or elapsed time)
+//               represented by this Collector, including all values
+//               in its child Collectors.
 ////////////////////////////////////////////////////////////////////
-double PStatViewLevel::
-get_net_time() const {
-  double net = _time_alone;
+float PStatViewLevel::
+get_net_value() const {
+  float net = _value_alone;
 
   Children::const_iterator ci;
   for (ci = _children.begin(); ci != _children.end(); ++ci) {
-    net += (*ci)->get_net_time();
+    net += (*ci)->get_net_value();
   }
 
   return net;
@@ -66,9 +66,9 @@ sort_children(const PStatClientData *client_data) {
 //     Function: PStatViewLevel::get_num_children
 //       Access: Public
 //  Description: Returns the number of children of this
-//               Level/Collector.  These are the Collectors whose time
-//               is considered to be part of the total time of this
-//               level's Collector.
+//               Level/Collector.  These are the Collectors whose
+//               value is considered to be part of the total value of
+//               this level's Collector.
 ////////////////////////////////////////////////////////////////////
 int PStatViewLevel::
 get_num_children() const {

+ 8 - 6
pandatool/src/pstatserver/pStatViewLevel.h

@@ -15,15 +15,17 @@ class PStatClientData;
 ////////////////////////////////////////////////////////////////////
 // 	 Class : PStatViewLevel
 // Description : This is a single level value, or band of color,
-//               within a View.  It generally indicates the elapsed
-//               time for a particular Collector within a given frame
-//               for a particular thread.
+//               within a View.  
+//
+//               It generally indicates either the elapsed time, or
+//               the "level" value, for a particular Collector within
+//               a given frame for a particular thread.
 ////////////////////////////////////////////////////////////////////
 class PStatViewLevel {
 public:
   INLINE int get_collector() const;
-  INLINE double get_time_alone() const;
-  double get_net_time() const;
+  INLINE float get_value_alone() const;
+  float get_net_value() const;
 
   void sort_children(const PStatClientData *client_data);
 
@@ -32,7 +34,7 @@ public:
 
 private:
   int _collector;
-  double _time_alone;
+  float _value_alone;
   PStatViewLevel *_parent;
 
   typedef vector<PStatViewLevel *> Children;

+ 2 - 2
pandatool/src/text-stats/textMonitor.cxx

@@ -61,7 +61,7 @@ new_data(int thread_index, int frame_number) {
       nout << "\rThread " 
 	   << get_client_data()->get_thread_name(thread_index)
 	   << " frame " << frame_number << ", "
-	   << view.get_net_time() * 1000.0 << " ms ("
+	   << view.get_net_value() * 1000.0 << " ms ("
 	   << thread_data->get_frame_rate() << " Hz):\n";
       const PStatViewLevel *level = view.get_top_level();
       int num_children = level->get_num_children();
@@ -113,7 +113,7 @@ show_level(const PStatViewLevel *level, int indent_level) {
 
   indent(nout, indent_level)
     << get_client_data()->get_collector_name(collector_index)
-    << " = " << level->get_net_time() * 1000.0 << " ms\n";
+    << " = " << level->get_net_value() * 1000.0 << " ms\n";
 
   int num_children = level->get_num_children();
   for (int i = 0; i < num_children; i++) {