Browse Source

add set_priority() to Light, PlaneNode

David Rose 20 years ago
parent
commit
512acd4adf

+ 32 - 0
panda/src/display/graphicsStateGuardian.I

@@ -233,6 +233,38 @@ get_max_cube_map_dimension() const {
   return _max_cube_map_dimension;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_lights
+//       Access: Published
+//  Description: Returns the maximum number of simultaneous lights
+//               that may be rendered on geometry, or -1 if there is
+//               no particular limit.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_lights() const {
+  return _max_lights;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_clip_planes
+//       Access: Published
+//  Description: Returns the maximum number of simultaneous clip planes
+//               that may be applied to geometry, or -1 if there is
+//               no particular limit.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_clip_planes() const {
+  return _max_clip_planes;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_max_vertex_transforms
 //       Access: Published

+ 38 - 67
panda/src/display/graphicsStateGuardian.cxx

@@ -120,6 +120,10 @@ GraphicsStateGuardian(const FrameBufferProperties &properties,
   _max_3d_texture_dimension = 0;
   _max_cube_map_dimension = 0;
 
+  // Assume no limits on number of lights or clip planes.
+  _max_lights = -1;
+  _max_clip_planes = -1;
+
   // Assume no vertex blending capability.
   _max_vertex_transforms = 0;
   _max_vertex_transform_indices = 0;
@@ -1163,23 +1167,25 @@ issue_texture(const TextureAttrib *attrib) {
 //     Function: GraphicsStateGuardian::issue_clip_plane
 //       Access: Public, Virtual
 //  Description: This is fundametically similar to issue_light(), with
-//               calls to slot_new_clip_plane(), apply_clip_plane(),
-//               and enable_clip_planes(), as appropriate.
+//               calls to apply_clip_plane() and enable_clip_planes(),
+//               as appropriate.
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
 issue_clip_plane(const ClipPlaneAttrib *attrib) {
   int i;
-  int max_planes = (int)_clip_plane_info.size();
-  for (i = 0; i < max_planes; i++) {
+  int cur_max_planes = (int)_clip_plane_info.size();
+  for (i = 0; i < cur_max_planes; i++) {
     _clip_plane_info[i]._next_enabled = false;
   }
 
+  CPT(ClipPlaneAttrib) new_attrib = attrib->filter_to_max(_max_clip_planes);
+
   bool any_bound = false;
 
   int num_enabled = 0;
-  int num_on_planes = attrib->get_num_on_planes();
+  int num_on_planes = new_attrib->get_num_on_planes();
   for (int li = 0; li < num_on_planes; li++) {
-    NodePath plane = attrib->get_on_plane(li);
+    NodePath plane = new_attrib->get_on_plane(li);
     nassertv(!plane.is_empty() && plane.node()->is_of_type(PlaneNode::get_class_type()));
 
     num_enabled++;
@@ -1191,7 +1197,7 @@ issue_clip_plane(const ClipPlaneAttrib *attrib) {
 
     // Check to see if this plane has already been bound to an id
     int cur_plane_id = -1;
-    for (i = 0; i < max_planes; i++) {
+    for (i = 0; i < cur_max_planes; i++) {
       if (_clip_plane_info[i]._plane == plane) {
         // Plane has already been bound to an id, we only need to
         // enable the plane, not reapply it.
@@ -1205,7 +1211,7 @@ issue_clip_plane(const ClipPlaneAttrib *attrib) {
         
     // See if there are any unbound plane ids
     if (cur_plane_id == -1) {
-      for (i = 0; i < max_planes; i++) {
+      for (i = 0; i < cur_max_planes; i++) {
         if (_clip_plane_info[i]._plane.is_empty()) {
           _clip_plane_info[i]._plane = plane;
           cur_plane_id = i;
@@ -1217,8 +1223,8 @@ issue_clip_plane(const ClipPlaneAttrib *attrib) {
     // If there were no unbound plane ids, see if we can replace
     // a currently unused but previously bound id
     if (cur_plane_id == -1) {
-      for (i = 0; i < max_planes; i++) {
-        if (!attrib->has_on_plane(_clip_plane_info[i]._plane)) {
+      for (i = 0; i < cur_max_planes; i++) {
+        if (!new_attrib->has_on_plane(_clip_plane_info[i]._plane)) {
           _clip_plane_info[i]._plane = plane;
           cur_plane_id = i;
           break;
@@ -1228,11 +1234,11 @@ issue_clip_plane(const ClipPlaneAttrib *attrib) {
 
     // If we *still* don't have a plane id, slot a new one.
     if (cur_plane_id == -1) {
-      if (slot_new_clip_plane(max_planes)) {
-        cur_plane_id = max_planes;
+      if (_max_clip_planes < 0 || cur_max_planes < _max_clip_planes) {
+        cur_plane_id = cur_max_planes;
         _clip_plane_info.push_back(ClipPlaneInfo());
-        max_planes++;
-        nassertv(max_planes == (int)_clip_plane_info.size());
+        cur_max_planes++;
+        nassertv(cur_max_planes == (int)_clip_plane_info.size());
       }
     }
         
@@ -1257,7 +1263,7 @@ issue_clip_plane(const ClipPlaneAttrib *attrib) {
   }
 
   // Disable all unused planes
-  for (i = 0; i < max_planes; i++) {
+  for (i = 0; i < cur_max_planes; i++) {
     if (!_clip_plane_info[i]._next_enabled) {
       enable_clip_plane(i, false);
       _clip_plane_info[i]._enabled = false;
@@ -1321,8 +1327,8 @@ bind_light(Spotlight *light_obj, const NodePath &light, int light_id) {
 //               light associated with the same id where possible, but
 //               reusing id's when necessary.  When it is no longer
 //               possible to reuse existing id's (e.g. all id's are in
-//               use), slot_new_light() is called to prepare the next
-//               sequential light id.
+//               use), the next sequential id is assigned (if
+//               available).
 //
 //               It will call apply_light() each time a light is
 //               assigned to a particular id for the first time in a
@@ -1337,8 +1343,8 @@ do_issue_light() {
   // light list
   Colorf cur_ambient_light(0.0f, 0.0f, 0.0f, 0.0f);
   int i;
-  int max_lights = (int)_light_info.size();
-  for (i = 0; i < max_lights; i++) {
+  int cur_max_lights = (int)_light_info.size();
+  for (i = 0; i < cur_max_lights; i++) {
     _light_info[i]._next_enabled = false;
   }
 
@@ -1346,9 +1352,11 @@ do_issue_light() {
 
   int num_enabled = 0;
   if (_pending_light != (LightAttrib *)NULL) {
-    int num_on_lights = _pending_light->get_num_on_lights();
+    CPT(LightAttrib) new_light = _pending_light->filter_to_max(_max_lights);
+
+    int num_on_lights = new_light->get_num_on_lights();
     for (int li = 0; li < num_on_lights; li++) {
-      NodePath light = _pending_light->get_on_light(li);
+      NodePath light = new_light->get_on_light(li);
       nassertv(!light.is_empty() && light.node()->as_light() != (Light *)NULL);
       Light *light_obj = light.node()->as_light();
       
@@ -1367,7 +1375,7 @@ do_issue_light() {
       } else {
         // Check to see if this light has already been bound to an id
         int cur_light_id = -1;
-        for (i = 0; i < max_lights; i++) {
+        for (i = 0; i < cur_max_lights; i++) {
           if (_light_info[i]._light == light) {
             // Light has already been bound to an id; reuse the same id.
             cur_light_id = -2;
@@ -1386,7 +1394,7 @@ do_issue_light() {
         
         // See if there are any unbound light ids
         if (cur_light_id == -1) {
-          for (i = 0; i < max_lights; i++) {
+          for (i = 0; i < cur_max_lights; i++) {
             if (_light_info[i]._light.is_empty()) {
               _light_info[i]._light = light;
               cur_light_id = i;
@@ -1398,8 +1406,8 @@ do_issue_light() {
         // If there were no unbound light ids, see if we can replace
         // a currently unused but previously bound id
         if (cur_light_id == -1) {
-          for (i = 0; i < max_lights; i++) {
-            if (!_pending_light->has_on_light(_light_info[i]._light)) {
+          for (i = 0; i < cur_max_lights; i++) {
+            if (!new_light->has_on_light(_light_info[i]._light)) {
               _light_info[i]._light = light;
               cur_light_id = i;
               break;
@@ -1409,11 +1417,11 @@ do_issue_light() {
         
         // If we *still* don't have a light id, slot a new one.
         if (cur_light_id == -1) {
-          if (slot_new_light(max_lights)) {
-            cur_light_id = max_lights;
+          if (_max_lights < 0 || cur_max_lights < _max_lights) {
+            cur_light_id = cur_max_lights;
             _light_info.push_back(LightInfo());
-            max_lights++;
-            nassertv(max_lights == (int)_light_info.size());
+            cur_max_lights++;
+            nassertv(cur_max_lights == (int)_light_info.size());
           }
         }
         
@@ -1440,7 +1448,7 @@ do_issue_light() {
   }
     
   // Disable all unused lights
-  for (i = 0; i < max_lights; i++) {
+  for (i = 0; i < cur_max_lights; i++) {
     if (!_light_info[i]._next_enabled) {
       enable_light(i, false);
       _light_info[i]._enabled = false;
@@ -1491,24 +1499,6 @@ void GraphicsStateGuardian::
 do_issue_texture() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::slot_new_light
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular light id will be used for the first time.
-//               It is intended to allow the derived class to reserve
-//               any additional resources, if required, for the new
-//               light; and also to indicate whether the hardware
-//               supports this many simultaneous lights.
-//
-//               The return value should be true if the additional
-//               light is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool GraphicsStateGuardian::
-slot_new_light(int light_id) {
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::enable_lighting
 //       Access: Protected, Virtual
@@ -1573,25 +1563,6 @@ void GraphicsStateGuardian::
 end_bind_lights() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::slot_new_clip_plane
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular clip plane id will be used for the first
-//               time.  It is intended to allow the derived class to
-//               reserve any additional resources, if required, for
-//               the new clip plane; and also to indicate whether the
-//               hardware supports this many simultaneous clipping
-//               planes.
-//
-//               The return value should be true if the additional
-//               plane is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool GraphicsStateGuardian::
-slot_new_clip_plane(int plane_id) {
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::enable_clip_planes
 //       Access: Protected, Virtual

+ 6 - 2
panda/src/display/graphicsStateGuardian.h

@@ -94,6 +94,9 @@ PUBLISHED:
   INLINE int get_max_3d_texture_dimension() const;
   INLINE int get_max_cube_map_dimension() const;
 
+  INLINE int get_max_lights() const;
+  INLINE int get_max_clip_planes() const;
+
   INLINE int get_max_vertex_transforms() const;
   INLINE int get_max_vertex_transform_indices() const;
 
@@ -225,7 +228,6 @@ protected:
   virtual void do_issue_texture();
 
   INLINE NodePath get_light(int light_id) const;
-  virtual bool slot_new_light(int light_id);
   virtual void enable_lighting(bool enable);
   virtual void set_ambient_light(const Colorf &color);
   virtual void enable_light(int light_id, bool enable);
@@ -233,7 +235,6 @@ protected:
   virtual void end_bind_lights();
 
   INLINE NodePath get_clip_plane(int plane_id) const;
-  virtual bool slot_new_clip_plane(int plane_id);
   virtual void enable_clip_planes(bool enable);
   virtual void enable_clip_plane(int plane_id, bool enable);
   virtual void begin_bind_clip_planes();
@@ -361,6 +362,9 @@ protected:
   int _max_3d_texture_dimension;
   int _max_cube_map_dimension;
 
+  int _max_lights;
+  int _max_clip_planes;
+
   int _max_vertex_transforms;
   int _max_vertex_transform_indices;
 

+ 2 - 39
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -1509,8 +1509,8 @@ reset() {
   }
 
   _max_texture_stages = d3d_caps.MaxSimultaneousTextures;
-  _max_lights = d3d_caps.MaxActiveLights;
-  _max_clip_planes = d3d_caps.MaxUserClipPlanes;
+  _max_lights = (int)d3d_caps.MaxActiveLights;
+  _max_clip_planes = (int)d3d_caps.MaxUserClipPlanes;
   _max_vertex_transforms = d3d_caps.MaxVertexBlendMatrices;
   _max_vertex_transform_indices = d3d_caps.MaxVertexBlendMatrixIndex;
 
@@ -2293,24 +2293,6 @@ do_issue_texture() {
   _current_texture = new_texture;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DXGraphicsStateGuardian8::slot_new_light
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular light id will be used for the first time.
-//               It is intended to allow the derived class to reserve
-//               any additional resources, if required, for the new
-//               light; and also to indicate whether the hardware
-//               supports this many simultaneous lights.
-//
-//               The return value should be true if the additional
-//               light is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool DXGraphicsStateGuardian8::
-slot_new_light(int light_id) {
-  return ((unsigned int)light_id < _max_lights);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DXGraphicsStateGuardian8::enable_lighting
 //       Access: Protected, Virtual
@@ -2361,25 +2343,6 @@ enable_light(int light_id, bool enable) {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DXGraphicsStateGuardian8::slot_new_clip_plane
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular clip plane id will be used for the first
-//               time.  It is intended to allow the derived class to
-//               reserve any additional resources, if required, for
-//               the new clip plane; and also to indicate whether the
-//               hardware supports this many simultaneous clipping
-//               planes.
-//
-//               The return value should be true if the additional
-//               plane is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool DXGraphicsStateGuardian8::
-slot_new_clip_plane(int plane_id) {
-  return ((unsigned int)plane_id < _max_clip_planes);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DXGraphicsStateGuardian8::enable_clip_plane
 //       Access: Protected, Virtual

+ 0 - 5
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -121,12 +121,10 @@ protected:
   virtual void do_issue_material();
   virtual void do_issue_texture();
 
-  virtual bool slot_new_light(int light_id);
   virtual void enable_lighting(bool enable);
   virtual void set_ambient_light(const Colorf &color);
   virtual void enable_light(int light_id, bool enable);
 
-  virtual bool slot_new_clip_plane(int plane_id);
   virtual void enable_clip_plane(int plane_id, bool enable);
   virtual void bind_clip_plane(const NodePath &plane, int plane_id);
 
@@ -223,9 +221,6 @@ protected:
   D3DBLEND _blend_source_func;
   D3DBLEND _blend_dest_func;
 
-  unsigned int _max_lights;
-  unsigned int _max_clip_planes;
-
   bool _color_material_enabled;
   bool _texturing_enabled;
   bool _dither_enabled;

+ 0 - 37
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -3795,24 +3795,6 @@ get_light_color(Light *light) const {
   return c.get_data();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GLGraphicsStateGuardian::slot_new_light
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular light id will be used for the first time.
-//               It is intended to allow the derived class to reserve
-//               any additional resources, if required, for the new
-//               light; and also to indicate whether the hardware
-//               supports this many simultaneous lights.
-//
-//               The return value should be true if the additional
-//               light is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool CLP(GraphicsStateGuardian)::
-slot_new_light(int light_id) {
-  return (light_id < _max_lights);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::enable_lighting
 //       Access: Protected, Virtual
@@ -3908,25 +3890,6 @@ end_bind_lights() {
   GLP(PopMatrix)();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GLGraphicsStateGuardian::slot_new_clip_plane
-//       Access: Protected, Virtual
-//  Description: This will be called by the base class before a
-//               particular clip plane id will be used for the first
-//               time.  It is intended to allow the derived class to
-//               reserve any additional resources, if required, for
-//               the new clip plane; and also to indicate whether the
-//               hardware supports this many simultaneous clipping
-//               planes.
-//
-//               The return value should be true if the additional
-//               plane is supported, or false if it is not.
-////////////////////////////////////////////////////////////////////
-bool CLP(GraphicsStateGuardian)::
-slot_new_clip_plane(int plane_id) {
-  return (plane_id < _max_clip_planes);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::enable_clip_plane
 //       Access: Protected, Virtual

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

@@ -170,14 +170,12 @@ protected:
   bool is_at_least_version(int major_version, int minor_version, int release_version = 0) const;
   virtual void *get_extension_func(const char *prefix, const char *name);
 
-  virtual bool slot_new_light(int light_id);
   virtual void enable_lighting(bool enable);
   virtual void set_ambient_light(const Colorf &color);
   virtual void enable_light(int light_id, bool enable);
   virtual void begin_bind_lights();
   virtual void end_bind_lights();
 
-  virtual bool slot_new_clip_plane(int plane_id);
   virtual void enable_clip_plane(int plane_id, bool enable);
   virtual void begin_bind_clip_planes();
   virtual void bind_clip_plane(const NodePath &plane, int plane_id);
@@ -284,9 +282,6 @@ protected:
   bool _dithering_enabled;
   bool _texgen_forced_normal;
 
-  int _max_lights;
-  int _max_clip_planes;
-
   LMatrix4f _projection_mat;
   int _viewport_width;
   int _viewport_height;

+ 1 - 1
panda/src/gobj/textureStage.I

@@ -617,7 +617,7 @@ get_default() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextureStage::get_sort_seq
-//       Access: Published, Static
+//       Access: Public, Static
 //  Description: Returns a global sequence number that is incremented
 //               any time any TextureStage in the world changes sort
 //               or priority.  This is used by TextureAttrib to

+ 2 - 0
panda/src/gobj/textureStage.h

@@ -158,6 +158,8 @@ PUBLISHED:
   void output(ostream &out) const;
 
   INLINE static TextureStage *get_default();
+
+public:
   INLINE static UpdateSeq get_sort_seq();
 
 private:

+ 15 - 0
panda/src/pgraph/ambientLight.cxx

@@ -48,6 +48,21 @@ AmbientLight(const AmbientLight &copy) :
 {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: AmbientLight::get_class_priority
+//       Access: Published, Virtual
+//  Description: Returns the relative priority associated with all
+//               lights of this class.  This priority is used to order
+//               lights whose instance priority (get_priority()) is
+//               the same--the idea is that other things being equal,
+//               AmbientLights (for instance) are less important than
+//               DirectionalLights.
+////////////////////////////////////////////////////////////////////
+int AmbientLight::
+get_class_priority() const {
+  return (int)CP_ambient_priority;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: AmbientLight::make_copy
 //       Access: Public, Virtual

+ 3 - 0
panda/src/pgraph/ambientLight.h

@@ -40,6 +40,9 @@ protected:
 public:
   virtual PandaNode *make_copy() const;
   virtual void write(ostream &out, int indent_level) const;
+
+PUBLISHED:
+  virtual int get_class_priority() const;
   
 public:
   virtual void bind(GraphicsStateGuardianBase *gsg, const NodePath &light,

+ 17 - 0
panda/src/pgraph/clipPlaneAttrib.I

@@ -133,3 +133,20 @@ INLINE bool ClipPlaneAttrib::
 is_identity() const {
   return _on_planes.empty() && _off_planes.empty() && !_off_all_planes;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: ClipPlaneAttrib::check_filtered
+//       Access: Private
+//  Description: Confirms whether the _filtered table is still valid.
+//               It may become invalid if someone calls
+//               PlaneNode::set_priority().
+//
+//               If the table is invalid, transparently empties it
+//               before returning.
+////////////////////////////////////////////////////////////////////
+INLINE void ClipPlaneAttrib::
+check_filtered() const {
+  if (_sort_seq != PlaneNode::get_sort_seq()) {
+    ((ClipPlaneAttrib *)this)->sort_on_planes();
+  }
+}

+ 83 - 0
panda/src/pgraph/clipPlaneAttrib.cxx

@@ -29,6 +29,20 @@ CPT(RenderAttrib) ClipPlaneAttrib::_empty_attrib;
 CPT(RenderAttrib) ClipPlaneAttrib::_all_off_attrib;
 TypeHandle ClipPlaneAttrib::_type_handle;
 
+// This STL Function object is used in filter_to_max(), below, to sort
+// a list of PlaneNodes in reverse order by priority.
+class ComparePlaneNodePriorities {
+public:
+  bool operator ()(const NodePath &a, const NodePath &b) const {
+    nassertr(!a.is_empty() && !b.is_empty(), a < b);
+    PlaneNode *pa = DCAST(PlaneNode, a.node());
+    PlaneNode *pb = DCAST(PlaneNode, b.node());
+    nassertr(pa != (PlaneNode *)NULL && pb != (PlaneNode *)NULL, a < b);
+             
+    return pa->get_priority() > pb->get_priority();
+  }
+};
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ClipPlaneAttrib::make
 //       Access: Published, Static
@@ -447,6 +461,61 @@ remove_off_plane(const NodePath &plane) const {
   return return_new(attrib);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ClipPlaneAttrib::filter_to_max
+//       Access: Public
+//  Description: Returns a new ClipPlaneAttrib, very much like this one,
+//               but with the number of on_planes reduced to be no
+//               more than max_clip_planes.  The number of off_planes in
+//               the new ClipPlaneAttrib is undefined.
+////////////////////////////////////////////////////////////////////
+CPT(ClipPlaneAttrib) ClipPlaneAttrib::
+filter_to_max(int max_clip_planes) const {
+  if (max_clip_planes < 0 || (int)_on_planes.size() <= max_clip_planes) {
+    // Trivial case: this ClipPlaneAttrib qualifies.
+    return this;
+  }
+
+  // Since check_filtered() will clear the _filtered list if we are out
+  // of date, we should call it first.
+  check_filtered();
+
+  Filtered::const_iterator fi;
+  fi = _filtered.find(max_clip_planes);
+  if (fi != _filtered.end()) {
+    // Easy case: we have already computed this for this particular
+    // ClipPlaneAttrib.
+    return (*fi).second;
+  }
+
+  // Harder case: we have to compute it now.  We must choose the n
+  // planeNodes with the highest priority in our list of planeNodes.
+  Planes priority_planes = _on_planes;
+
+  // This sort function uses the STL function object defined above.
+  sort(priority_planes.begin(), priority_planes.end(), 
+       ComparePlaneNodePriorities());
+
+  // Now lop off all of the planeNodes after the first max_clip_planes.
+  priority_planes.erase(priority_planes.begin() + max_clip_planes,
+                        priority_planes.end());
+
+  // And re-sort the ov_set into its proper order.
+  priority_planes.sort();
+
+  // Now create a new attrib reflecting these planeNodes.
+  PT(ClipPlaneAttrib) attrib = new ClipPlaneAttrib;
+  attrib->_on_planes.swap(priority_planes);
+
+  CPT(RenderAttrib) new_attrib = return_new(attrib);
+
+  // Finally, record this newly-created attrib in the map for next
+  // time.
+  CPT(ClipPlaneAttrib) planeNode_attrib = (const ClipPlaneAttrib *)new_attrib.p();
+  ((ClipPlaneAttrib *)this)->_filtered[max_clip_planes] = planeNode_attrib;
+  return planeNode_attrib;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ClipPlaneAttrib::issue
 //       Access: Public, Virtual
@@ -749,6 +818,20 @@ make_default_impl() const {
   return new ClipPlaneAttrib;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ClipPlaneAttrib::sort_on_planes
+//       Access: Private
+//  Description: This is patterned after
+//               TextureAttrib::sort_on_stages(), but since planeNodes
+//               don't actually require sorting, this only empties the
+//               _filtered map.
+////////////////////////////////////////////////////////////////////
+void ClipPlaneAttrib::
+sort_on_planes() {
+  _sort_seq = PlaneNode::get_sort_seq();
+  _filtered.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ClipPlaneAttrib::register_with_read_factory
 //       Access: Public, Static

+ 13 - 1
panda/src/pgraph/clipPlaneAttrib.h

@@ -23,8 +23,9 @@
 
 #include "planeNode.h"
 #include "renderAttrib.h"
-#include "ordered_vector.h"
 #include "nodePath.h"
+#include "ordered_vector.h"
+#include "pmap.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ClipPlaneAttrib
@@ -92,6 +93,8 @@ PUBLISHED:
   CPT(RenderAttrib) add_off_plane(const NodePath &plane) const;
   CPT(RenderAttrib) remove_off_plane(const NodePath &plane) const;
 
+  CPT(ClipPlaneAttrib) filter_to_max(int max_clip_planes) const;
+
 public:
   virtual void issue(GraphicsStateGuardianBase *gsg) const;
   virtual void output(ostream &out) const;
@@ -102,10 +105,19 @@ protected:
   virtual CPT(RenderAttrib) invert_compose_impl(const RenderAttrib *other) const;
   virtual RenderAttrib *make_default_impl() const;
 
+private:
+  INLINE void check_filtered() const;
+  void sort_on_planes();
+
 private:
   typedef ov_set<NodePath> Planes;
   Planes _on_planes, _off_planes;
   bool _off_all_planes;
+  
+  typedef pmap< int, CPT(ClipPlaneAttrib) > Filtered;
+  Filtered _filtered;
+
+  UpdateSeq _sort_seq;
 
   static CPT(RenderAttrib) _empty_attrib;
   static CPT(RenderAttrib) _all_off_attrib;

+ 15 - 0
panda/src/pgraph/directionalLight.cxx

@@ -159,6 +159,21 @@ get_vector_to_light(LVector3f &result, const LPoint3f &,
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DirectionalLight::get_class_priority
+//       Access: Published, Virtual
+//  Description: Returns the relative priority associated with all
+//               lights of this class.  This priority is used to order
+//               lights whose instance priority (get_priority()) is
+//               the same--the idea is that other things being equal,
+//               AmbientLights (for instance) are less important than
+//               DirectionalLights.
+////////////////////////////////////////////////////////////////////
+int DirectionalLight::
+get_class_priority() const {
+  return (int)CP_directional_priority;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DirectionalLight::bind
 //       Access: Public, Virtual

+ 2 - 0
panda/src/pgraph/directionalLight.h

@@ -53,6 +53,8 @@ PUBLISHED:
   
   INLINE const LVector3f &get_direction() const;
   INLINE void set_direction(const LVector3f &direction);
+
+  virtual int get_class_priority() const;
   
 public:
   virtual void bind(GraphicsStateGuardianBase *gsg, const NodePath &light,

+ 54 - 2
panda/src/pgraph/light.I

@@ -48,7 +48,9 @@ CData(const Light::CData &copy) :
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Light::
-Light() {
+Light() :
+  _priority(0)
+{
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -58,6 +60,7 @@ Light() {
 ////////////////////////////////////////////////////////////////////
 INLINE Light::
 Light(const Light &copy) :
+  _priority(copy._priority),
   _cycler(copy._cycler)
 {
 }
@@ -85,9 +88,58 @@ set_color(const Colorf &color) {
   mark_viz_stale();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Light::set_priority
+//       Access: Published
+//  Description: Changes the relative importance of this light
+//               relative to the other lights that are applied
+//               simultaneously.
+//
+//               The priority number is used to decide which of the
+//               requested lights are to be selected for rendering
+//               when more lights are requested than the hardware will
+//               support.  The highest-priority n lights are selected
+//               for rendering.
+//
+//               This is similar to TextureStage::set_priority().
+////////////////////////////////////////////////////////////////////
+INLINE void Light::
+set_priority(int priority) {
+  _priority = priority;
+
+  // Update the global flag to indicate that all LightAttribs in the
+  // world must now re-sort their lists.
+  _sort_seq++;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Light::get_priority
+//       Access: Published
+//  Description: Returns the priority associated with this light.  See
+//               set_priority().
+////////////////////////////////////////////////////////////////////
+INLINE int Light::
+get_priority() const {
+  return _priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Light::get_sort_seq
+//       Access: Public, Static
+//  Description: Returns a global sequence number that is incremented
+//               any time any Light in the world changes sort
+//               or priority.  This is used by LightAttrib to
+//               determine when it is necessary to re-sort its
+//               internal array of stages.
+////////////////////////////////////////////////////////////////////
+INLINE UpdateSeq Light::
+get_sort_seq() {
+  return _sort_seq;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Light::mark_viz_stale
-//       Access: Public
+//       Access: Protected
 //  Description: Indicates that the internal visualization object will
 //               need to be updated.
 ////////////////////////////////////////////////////////////////////

+ 4 - 0
panda/src/pgraph/light.cxx

@@ -22,6 +22,8 @@
 #include "datagram.h"
 #include "datagramIterator.h"
 
+UpdateSeq Light::_sort_seq;
+
 TypeHandle Light::_type_handle;
 
 
@@ -128,6 +130,7 @@ fill_viz_geom(GeomNode *) {
 void Light::
 write_datagram(BamWriter *manager, Datagram &dg) {
   manager->write_cdata(dg, _cycler);
+  dg.add_int32(_priority);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -140,4 +143,5 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 void Light::
 fillin(DatagramIterator &scan, BamReader *manager) {
   manager->read_cdata(scan, _cycler);
+  _priority = scan.get_int32();
 }

+ 25 - 0
panda/src/pgraph/light.h

@@ -27,6 +27,7 @@
 #include "cycleDataReader.h"
 #include "cycleDataWriter.h"
 #include "pipelineCycler.h"
+#include "updateSeq.h"
 #include "geomNode.h"
 
 class NodePath;
@@ -56,6 +57,10 @@ PUBLISHED:
   INLINE const Colorf &get_color() const;
   INLINE void set_color(const Colorf &color);
 
+  INLINE void set_priority(int priority);
+  INLINE int get_priority() const;
+  virtual int get_class_priority() const=0;
+
 public:
   virtual void output(ostream &out) const=0;
   virtual void write(ostream &out, int indent_level) const=0;
@@ -68,11 +73,31 @@ public:
 
   GeomNode *get_viz();
 
+  INLINE static UpdateSeq get_sort_seq();
+
 protected:
   virtual void fill_viz_geom(GeomNode *viz_geom);
   INLINE void mark_viz_stale();
+  
+  // This enumerated class defines the relative class priority of
+  // different kinds of lights.  This hierarchy is only used to
+  // resolve multiple lights of the same priority specified by
+  // set_priority().  In general, the first items in this list have a
+  // lesser priority than later items.
+  enum ClassPriority {
+    CP_ambient_priority,
+    CP_point_priority,
+    CP_directional_priority,
+    CP_spot_priority,
+  };
 
 private:
+  // The priority is not cycled, because there's no real reason to do
+  // so, and cycling it makes it difficult to synchronize with the
+  // LightAttribs.
+  int _priority;
+  static UpdateSeq _sort_seq;
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   public:

+ 17 - 0
panda/src/pgraph/lightAttrib.I

@@ -133,3 +133,20 @@ INLINE bool LightAttrib::
 is_identity() const {
   return _on_lights.empty() && _off_lights.empty() && !_off_all_lights;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: LightAttrib::check_filtered
+//       Access: Private
+//  Description: Confirms whether the _filtered table is still valid.
+//               It may become invalid if someone calls
+//               Light::set_priority().
+//
+//               If the table is invalid, transparently empties it
+//               before returning.
+////////////////////////////////////////////////////////////////////
+INLINE void LightAttrib::
+check_filtered() const {
+  if (_sort_seq != Light::get_sort_seq()) {
+    ((LightAttrib *)this)->sort_on_lights();
+  }
+}

+ 110 - 0
panda/src/pgraph/lightAttrib.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "lightAttrib.h"
+#include "ambientLight.h"
 #include "pandaNode.h"
 #include "nodePath.h"
 #include "graphicsStateGuardianBase.h"
@@ -30,6 +31,24 @@ CPT(RenderAttrib) LightAttrib::_empty_attrib;
 CPT(RenderAttrib) LightAttrib::_all_off_attrib;
 TypeHandle LightAttrib::_type_handle;
 
+// This STL Function object is used in filter_to_max(), below, to sort
+// a list of Lights in reverse order by priority.  In the case of two
+// lights with equal priority, the class priority is compared.
+class CompareLightPriorities {
+public:
+  bool operator ()(const NodePath &a, const NodePath &b) const {
+    nassertr(!a.is_empty() && !b.is_empty(), a < b);
+    Light *la = a.node()->as_light();
+    Light *lb = b.node()->as_light();
+    nassertr(la != (Light *)NULL && lb != (Light *)NULL, a < b);
+             
+    if (la->get_priority() != lb->get_priority()) {
+      return la->get_priority() > lb->get_priority();
+    }
+    return la->get_class_priority() > lb->get_class_priority();
+  }
+};
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LightAttrib::make
 //       Access: Published, Static
@@ -448,6 +467,83 @@ remove_off_light(const NodePath &light) const {
   return return_new(attrib);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: LightAttrib::filter_to_max
+//       Access: Public
+//  Description: Returns a new LightAttrib, very much like this one,
+//               but with the number of on_lights reduced to be no
+//               more than max_lights.  The number of off_lights in
+//               the new LightAttrib is undefined.
+//
+//               The number of AmbientLights is not included in the
+//               count.  All AmbientLights in the original attrib are
+//               always included in the result, regardless of the
+//               value of max_lights.
+////////////////////////////////////////////////////////////////////
+CPT(LightAttrib) LightAttrib::
+filter_to_max(int max_lights) const {
+  if (max_lights < 0 || (int)_on_lights.size() <= max_lights) {
+    // Trivial case: this LightAttrib qualifies.
+    return this;
+  }
+
+  // Since check_filtered() will clear the _filtered list if we are out
+  // of date, we should call it first.
+  check_filtered();
+
+  Filtered::const_iterator fi;
+  fi = _filtered.find(max_lights);
+  if (fi != _filtered.end()) {
+    // Easy case: we have already computed this for this particular
+    // LightAttrib.
+    return (*fi).second;
+  }
+
+  // Harder case: we have to compute it now.  We must choose the n
+  // lights with the highest priority in our list of lights.
+  Lights priority_lights, ambient_lights;
+
+  // Separate the list of lights into ambient lights and other lights.
+  Lights::const_iterator li;
+  for (li = _on_lights.begin(); li != _on_lights.end(); ++li) {
+    const NodePath &np = (*li);
+    nassertr(!np.is_empty() && np.node()->as_light() != (Light *)NULL, this);
+    if (np.node()->is_exact_type(AmbientLight::get_class_type())) {
+      ambient_lights.push_back(np);
+    } else {
+      priority_lights.push_back(np);
+    }
+  }
+
+  // This sort function uses the STL function object defined above.
+  sort(priority_lights.begin(), priority_lights.end(), 
+       CompareLightPriorities());
+
+  // Now lop off all of the lights after the first max_lights.
+  priority_lights.erase(priority_lights.begin() + max_lights,
+                        priority_lights.end());
+
+  // Put the ambient lights back into the list.
+  for (li = ambient_lights.begin(); li != ambient_lights.end(); ++li) {
+    priority_lights.push_back(*li);
+  }
+
+  // And re-sort the ov_set into its proper order.
+  priority_lights.sort();
+
+  // Now create a new attrib reflecting these lights.
+  PT(LightAttrib) attrib = new LightAttrib;
+  attrib->_on_lights.swap(priority_lights);
+
+  CPT(RenderAttrib) new_attrib = return_new(attrib);
+
+  // Finally, record this newly-created attrib in the map for next
+  // time.
+  CPT(LightAttrib) light_attrib = (const LightAttrib *)new_attrib.p();
+  ((LightAttrib *)this)->_filtered[max_lights] = light_attrib;
+  return light_attrib;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LightAttrib::issue
 //       Access: Public, Virtual
@@ -750,6 +846,20 @@ make_default_impl() const {
   return new LightAttrib;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: LightAttrib::sort_on_lights
+//       Access: Private
+//  Description: This is patterned after
+//               TextureAttrib::sort_on_stages(), but since lights
+//               don't actually require sorting, this only empties the
+//               _filtered map.
+////////////////////////////////////////////////////////////////////
+void LightAttrib::
+sort_on_lights() {
+  _sort_seq = Light::get_sort_seq();
+  _filtered.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LightAttrib::register_with_read_factory
 //       Access: Public, Static

+ 13 - 1
panda/src/pgraph/lightAttrib.h

@@ -23,8 +23,9 @@
 
 #include "light.h"
 #include "renderAttrib.h"
-#include "ordered_vector.h"
 #include "nodePath.h"
+#include "ordered_vector.h"
+#include "pmap.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : LightAttrib
@@ -90,6 +91,8 @@ PUBLISHED:
   CPT(RenderAttrib) add_off_light(const NodePath &light) const;
   CPT(RenderAttrib) remove_off_light(const NodePath &light) const;
 
+  CPT(LightAttrib) filter_to_max(int max_lights) const;
+
 public:
   virtual void issue(GraphicsStateGuardianBase *gsg) const;
   virtual void output(ostream &out) const;
@@ -100,11 +103,20 @@ protected:
   virtual CPT(RenderAttrib) invert_compose_impl(const RenderAttrib *other) const;
   virtual RenderAttrib *make_default_impl() const;
 
+private:
+  INLINE void check_filtered() const;
+  void sort_on_lights();
+
 private:
   typedef ov_set<NodePath> Lights;
   Lights _on_lights, _off_lights;
   bool _off_all_lights;
 
+  typedef pmap< int, CPT(LightAttrib) > Filtered;
+  Filtered _filtered;
+
+  UpdateSeq _sort_seq;
+
   static CPT(RenderAttrib) _empty_attrib;
   static CPT(RenderAttrib) _all_off_attrib;
 

+ 50 - 0
panda/src/pgraph/planeNode.I

@@ -61,3 +61,53 @@ get_plane() const {
   CDReader cdata(_cycler);
   return cdata->_plane;
 }
+ 
+////////////////////////////////////////////////////////////////////
+//     Function: PlaneNode::set_priority
+//       Access: Published
+//  Description: Changes the relative importance of this PlaneNode
+//               (when it is used as a clip plane) relative to the
+//               other clip planes that are applied simultaneously.
+//
+//               The priority number is used to decide which of the
+//               requested clip planes are to be activated when more
+//               clip planes are requested than the hardware will
+//               support.  The highest-priority n planes are selected
+//               for rendering.
+//
+//               This is similar to TextureStage::set_priority().
+////////////////////////////////////////////////////////////////////
+INLINE void PlaneNode::
+set_priority(int priority) {
+  _priority = priority;
+
+  // Update the global flag to indicate that all ClipPlaneAttribs in
+  // the world must now re-sort their lists.
+  _sort_seq++;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PlaneNode::get_priority
+//       Access: Published
+//  Description: Returns the priority associated with this clip
+//               plane.  See set_priority().
+////////////////////////////////////////////////////////////////////
+INLINE int PlaneNode::
+get_priority() const {
+  return _priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PlaneNode::get_sort_seq
+//       Access: Public, Static
+//  Description: Returns a global sequence number that is incremented
+//               any time any PlaneNode in the world changes sort
+//               or priority.  This is used by ClipPlaneAttrib to
+//               determine when it is necessary to re-sort its
+//               internal array of stages.
+////////////////////////////////////////////////////////////////////
+INLINE UpdateSeq PlaneNode::
+get_sort_seq() {
+  return _sort_seq;
+}
+

+ 7 - 1
panda/src/pgraph/planeNode.cxx

@@ -23,6 +23,8 @@
 #include "datagram.h"
 #include "datagramIterator.h"
 
+UpdateSeq PlaneNode::_sort_seq;
+
 TypeHandle PlaneNode::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
@@ -65,7 +67,8 @@ fillin(DatagramIterator &scan, BamReader *) {
 ////////////////////////////////////////////////////////////////////
 PlaneNode::
 PlaneNode(const string &name) :
-  PandaNode(name)
+  PandaNode(name),
+  _priority(0)
 {
 }
 
@@ -77,6 +80,7 @@ PlaneNode(const string &name) :
 PlaneNode::
 PlaneNode(const PlaneNode &copy) :
   PandaNode(copy),
+  _priority(copy._priority),
   _cycler(copy._cycler)
 {
 }
@@ -151,6 +155,7 @@ void PlaneNode::
 write_datagram(BamWriter *manager, Datagram &dg) {
   PandaNode::write_datagram(manager, dg);
   manager->write_cdata(dg, _cycler);
+  dg.add_int32(_priority);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -184,4 +189,5 @@ void PlaneNode::
 fillin(DatagramIterator &scan, BamReader *manager) {
   PandaNode::fillin(scan, manager);
   manager->read_cdata(scan, _cycler);
+  _priority = scan.get_int32();
 }

+ 13 - 0
panda/src/pgraph/planeNode.h

@@ -23,6 +23,7 @@
 
 #include "plane.h"
 #include "pandaNode.h"
+#include "updateSeq.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PlaneNode
@@ -48,7 +49,19 @@ PUBLISHED:
   INLINE void set_plane(const Planef &plane);
   INLINE const Planef &get_plane() const;
 
+  INLINE void set_priority(int priority);
+  INLINE int get_priority() const;
+
+public:
+  INLINE static UpdateSeq get_sort_seq();
+  
 private:
+  // The priority is not cycled, because there's no real reason to do
+  // so, and cycling it makes it difficult to synchronize with the
+  // ClipPlaneAttribs.
+  int _priority;
+  static UpdateSeq _sort_seq;
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   public:

+ 15 - 0
panda/src/pgraph/pointLight.cxx

@@ -159,6 +159,21 @@ get_vector_to_light(LVector3f &result, const LPoint3f &from_object_point,
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PointLight::get_class_priority
+//       Access: Published, Virtual
+//  Description: Returns the relative priority associated with all
+//               lights of this class.  This priority is used to order
+//               lights whose instance priority (get_priority()) is
+//               the same--the idea is that other things being equal,
+//               AmbientLights (for instance) are less important than
+//               DirectionalLights.
+////////////////////////////////////////////////////////////////////
+int PointLight::
+get_class_priority() const {
+  return (int)CP_point_priority;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointLight::bind
 //       Access: Public, Virtual

+ 2 - 0
panda/src/pgraph/pointLight.h

@@ -53,6 +53,8 @@ PUBLISHED:
   
   INLINE const LPoint3f &get_point() const;
   INLINE void set_point(const LPoint3f &point);
+
+  virtual int get_class_priority() const;
   
 public:
   virtual void bind(GraphicsStateGuardianBase *gsg, const NodePath &light,

+ 15 - 0
panda/src/pgraph/spotlight.cxx

@@ -212,6 +212,21 @@ make_spot(int pixel_width, float full_radius, Colorf &fg, Colorf &bg) {
   return tex;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Spotlight::get_class_priority
+//       Access: Published, Virtual
+//  Description: Returns the relative priority associated with all
+//               lights of this class.  This priority is used to order
+//               lights whose instance priority (get_priority()) is
+//               the same--the idea is that other things being equal,
+//               AmbientLights (for instance) are less important than
+//               DirectionalLights.
+////////////////////////////////////////////////////////////////////
+int Spotlight::
+get_class_priority() const {
+  return (int)CP_spot_priority;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Spotlight::bind
 //       Access: Public, Virtual

+ 2 - 0
panda/src/pgraph/spotlight.h

@@ -65,6 +65,8 @@ PUBLISHED:
   INLINE const LVecBase3f &get_attenuation() const;
   INLINE void set_attenuation(const LVecBase3f &attenuation);
 
+  virtual int get_class_priority() const;
+
   static PT(Texture) make_spot(int pixel_width, float full_radius,
                                Colorf &fg, Colorf &bg);
   

+ 2 - 2
panda/src/pgraph/textureAttrib.cxx

@@ -30,7 +30,7 @@ TypeHandle TextureAttrib::_type_handle;
 
 // This STL Function object is used in filter_to_max(), below, to sort
 // a list of TextureStages in reverse order by priority and, within
-// prioirty, in order by sort.
+// priority, in order by sort.
 class CompareTextureStagePriorities {
 public:
   bool operator ()(const TextureStage *a, const TextureStage *b) const {
@@ -778,7 +778,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 //     Function: TextureAttrib::sort_on_stages
 //       Access: Private
 //  Description: Sorts the list of stages so that they are listed in
-//               render order.
+//               render order.  Also clears the _filtered map.
 ////////////////////////////////////////////////////////////////////
 void TextureAttrib::
 sort_on_stages() {