Browse Source

Merge branch 'master' into vulkan

rdb 7 years ago
parent
commit
dcc5d4d015
55 changed files with 1643 additions and 771 deletions
  1. 1 1
      .travis.yml
  2. 2 2
      direct/src/stdpy/threading.py
  3. 2 1
      direct/src/tkpanels/ParticlePanel.py
  4. 224 222
      dtool/src/cppparser/cppBison.cxx.prebuilt
  5. 2 2
      dtool/src/cppparser/cppBison.h.prebuilt
  6. 27 5
      dtool/src/cppparser/cppBison.yxx
  7. 4 0
      dtool/src/dtoolbase/dtoolbase_cc.h
  8. 1 1
      dtool/src/parser-inc/ws2tcpip.h
  9. 0 3
      makepanda/makepanda.vcproj
  10. 4 1
      makepanda/makewheel.py
  11. 1 1
      panda/src/chan/partBundle.h
  12. 5 1
      panda/src/char/characterJointEffect.I
  13. 145 0
      panda/src/collide/collisionBox.cxx
  14. 2 0
      panda/src/collide/collisionBox.h
  15. 2 0
      panda/src/collide/collisionTube.h
  16. 0 70
      panda/src/collide/test_collide.cxx
  17. 3 5
      panda/src/cull/cullBinBackToFront.cxx
  18. 3 5
      panda/src/cull/cullBinFixed.cxx
  19. 3 5
      panda/src/cull/cullBinFrontToBack.cxx
  20. 3 5
      panda/src/cull/cullBinStateSorted.cxx
  21. 3 5
      panda/src/cull/cullBinUnsorted.cxx
  22. 10 4
      panda/src/dxgsg9/dxGeomMunger9.cxx
  23. 20 0
      panda/src/express/pointerTo.h
  24. 1 0
      panda/src/express/pointerToBase.h
  25. 0 7
      panda/src/express/pointerToVoid.I
  26. 2 2
      panda/src/express/pointerToVoid.h
  27. 264 34
      panda/src/express/weakPointerTo.I
  28. 77 2
      panda/src/express/weakPointerTo.h
  29. 191 33
      panda/src/express/weakPointerToBase.I
  30. 21 1
      panda/src/express/weakPointerToBase.h
  31. 6 9
      panda/src/express/weakPointerToVoid.I
  32. 2 2
      panda/src/express/weakPointerToVoid.h
  33. 20 8
      panda/src/glstuff/glGeomMunger_src.cxx
  34. 11 7
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  35. 1 0
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  36. 0 12
      panda/src/gobj/config_gobj.cxx
  37. 0 1
      panda/src/gobj/config_gobj.h
  38. 49 12
      panda/src/gobj/geom.cxx
  39. 11 3
      panda/src/gobj/geomPrimitive.I
  40. 1 0
      panda/src/gobj/geomPrimitive.h
  41. 43 15
      panda/src/gobj/material.I
  42. 25 34
      panda/src/gobj/material.cxx
  43. 6 0
      panda/src/gobj/material.h
  44. 0 25
      panda/src/gobj/test_gobj.cxx
  45. 20 0
      panda/src/gobj/texturePool.I
  46. 129 110
      panda/src/gobj/texturePool.cxx
  47. 11 2
      panda/src/gobj/texturePool.h
  48. 4 4
      panda/src/linmath/coordinateSystem.h
  49. 3 1
      panda/src/nativenet/socket_portable.h
  50. 4 10
      panda/src/pgraph/nodePath.cxx
  51. 9 12
      panda/src/pgraph/weakNodePath.I
  52. 27 8
      panda/src/pgraphnodes/shaderGenerator.cxx
  53. 0 78
      panda/src/testbed/text_test.cxx
  54. 42 0
      tests/gobj/test_geom.py
  55. 196 0
      tests/gobj/test_texture_pool.py

+ 1 - 1
.travis.yml

@@ -51,4 +51,4 @@ notifications:
     on_success: change
     on_success: change
     on_failure: always
     on_failure: always
     use_notice: true
     use_notice: true
-    skip_join: true
+    skip_join: false

+ 2 - 2
direct/src/stdpy/threading.py

@@ -312,7 +312,7 @@ class Event:
     object. """
     object. """
 
 
     def __init__(self):
     def __init__(self):
-        self.__lock = core.Lock("Python Event")
+        self.__lock = core.Mutex("Python Event")
         self.__cvar = core.ConditionVarFull(self.__lock)
         self.__cvar = core.ConditionVarFull(self.__lock)
         self.__flag = False
         self.__flag = False
 
 
@@ -325,7 +325,7 @@ class Event:
         self.__lock.acquire()
         self.__lock.acquire()
         try:
         try:
             self.__flag = True
             self.__flag = True
-            self.__cvar.signalAll()
+            self.__cvar.notifyAll()
 
 
         finally:
         finally:
             self.__lock.release()
             self.__lock.release()

+ 2 - 1
direct/src/tkpanels/ParticlePanel.py

@@ -263,7 +263,8 @@ class ParticlePanel(AppShell):
             'Factory', 'Factory Type',
             'Factory', 'Factory Type',
             'Select type of particle factory',
             'Select type of particle factory',
             ('PointParticleFactory', 'ZSpinParticleFactory',
             ('PointParticleFactory', 'ZSpinParticleFactory',
-             'OrientedParticleFactory'),
+             #'OrientedParticleFactory'
+             ),
             self.selectFactoryType)
             self.selectFactoryType)
         factoryWidgets = (
         factoryWidgets = (
             ('Factory', 'Life Span',
             ('Factory', 'Life Span',

File diff suppressed because it is too large
+ 224 - 222
dtool/src/cppparser/cppBison.cxx.prebuilt


+ 2 - 2
dtool/src/cppparser/cppBison.h.prebuilt

@@ -1,8 +1,8 @@
-/* A Bison parser, made by GNU Bison 3.0.4.  */
+/* A Bison parser, made by GNU Bison 3.0.5.  */
 
 
 /* Bison interface for Yacc-like parsers in C
 /* Bison interface for Yacc-like parsers in C
 
 
-   Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
 
 
    This program is free software: you can redistribute it and/or modify
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    it under the terms of the GNU General Public License as published by

+ 27 - 5
dtool/src/cppparser/cppBison.yxx

@@ -1185,14 +1185,27 @@ constructor_prototype:
 /* Functions with implicit return types, and constructors */
 /* Functions with implicit return types, and constructors */
         IDENTIFIER '('
         IDENTIFIER '('
 {
 {
-  push_scope($1->get_scope(current_scope, global_scope));
+  // Create a scope for this function.
+  CPPScope *scope = new CPPScope($1->get_scope(current_scope, global_scope),
+                                 $1->_names.back(), V_private);
+
+  // It still needs to be able to pick up any template arguments, if this is
+  // a definition for a method template.  Add a fake "using" declaration to
+  // accomplish this.
+  scope->_using.insert(current_scope);
+
+  push_scope(scope);
 }
 }
         function_parameter_list ')' function_post
         function_parameter_list ')' function_post
 {
 {
+  CPPScope *scope = $1->get_scope(current_scope, global_scope);
   CPPType *type;
   CPPType *type;
-  if ($1->get_simple_name() == current_scope->get_simple_name() ||
-      $1->get_simple_name() == string("~") + current_scope->get_simple_name()) {
-    // This is a constructor, and has no return.
+  std::string simple_name = $1->get_simple_name();
+  if (!simple_name.empty() && simple_name[0] == '~') {
+    // A destructor has no return type.
+    type = new CPPSimpleType(CPPSimpleType::T_void);
+  } else if (scope != nullptr && simple_name == scope->get_simple_name()) {
+    // Neither does a constructor.
     type = new CPPSimpleType(CPPSimpleType::T_void);
     type = new CPPSimpleType(CPPSimpleType::T_void);
   } else {
   } else {
     // This isn't a constructor, so it has an implicit return type of
     // This isn't a constructor, so it has an implicit return type of
@@ -1209,7 +1222,16 @@ constructor_prototype:
 }
 }
         | TYPENAME_IDENTIFIER '('
         | TYPENAME_IDENTIFIER '('
 {
 {
-  push_scope($1->get_scope(current_scope, global_scope));
+  // Create a scope for this function.
+  CPPScope *scope = new CPPScope($1->get_scope(current_scope, global_scope),
+                                 $1->_names.back(), V_private);
+
+  // It still needs to be able to pick up any template arguments, if this is
+  // a definition for a method template.  Add a fake "using" declaration to
+  // accomplish this.
+  scope->_using.insert(current_scope);
+
+  push_scope(scope);
 }
 }
         function_parameter_list ')' function_post
         function_parameter_list ')' function_post
 {
 {

+ 4 - 0
dtool/src/dtoolbase/dtoolbase_cc.h

@@ -97,10 +97,12 @@ typedef std::ios::seekdir ios_seekdir;
 // in some important missing functions.
 // in some important missing functions.
 #if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
 #if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
 #include <tr1/tuple>
 #include <tr1/tuple>
+#include <tr1/cmath>
 
 
 namespace std {
 namespace std {
   using std::tr1::tuple;
   using std::tr1::tuple;
   using std::tr1::tie;
   using std::tr1::tie;
+  using std::tr1::copysign;
 
 
   typedef decltype(nullptr) nullptr_t;
   typedef decltype(nullptr) nullptr_t;
 
 
@@ -111,6 +113,8 @@ namespace std {
   template<class T> typename remove_reference<T>::type &&move(T &&t) {
   template<class T> typename remove_reference<T>::type &&move(T &&t) {
     return static_cast<typename remove_reference<T>::type&&>(t);
     return static_cast<typename remove_reference<T>::type&&>(t);
   }
   }
+
+  template<class T> struct owner_less;
 };
 };
 #endif
 #endif
 
 

+ 1 - 1
dtool/src/parser-inc/ws2tcpip.h

@@ -1 +1 @@
-typedef DWORD socklen_t;
+typedef int socklen_t;

+ 0 - 3
makepanda/makepanda.vcproj

@@ -760,7 +760,6 @@
 				<File RelativePath="..\panda\src\gobj\bufferContext.cxx"></File>
 				<File RelativePath="..\panda\src\gobj\bufferContext.cxx"></File>
 				<File RelativePath="..\panda\src\gobj\textureContext.I"></File>
 				<File RelativePath="..\panda\src\gobj\textureContext.I"></File>
 				<File RelativePath="..\panda\src\gobj\internalName.h"></File>
 				<File RelativePath="..\panda\src\gobj\internalName.h"></File>
-				<File RelativePath="..\panda\src\gobj\test_gobj.cxx"></File>
 				<File RelativePath="..\panda\src\gobj\geomTristrips.h"></File>
 				<File RelativePath="..\panda\src\gobj\geomTristrips.h"></File>
 				<File RelativePath="..\panda\src\gobj\textureContext.h"></File>
 				<File RelativePath="..\panda\src\gobj\textureContext.h"></File>
 				<File RelativePath="..\panda\src\gobj\config_gobj.cxx"></File>
 				<File RelativePath="..\panda\src\gobj\config_gobj.cxx"></File>
@@ -1114,7 +1113,6 @@
 				<File RelativePath="..\panda\src\collide\collisionHandlerGravity.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerGravity.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionLine.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionLine.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerPhysical.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerPhysical.I"></File>
-				<File RelativePath="..\panda\src\collide\test_collide.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionFloorMesh.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionFloorMesh.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionPolygon.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionPolygon.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionGeom.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionGeom.cxx"></File>
@@ -3705,7 +3703,6 @@
 				<File RelativePath="..\panda\src\testbed\test_map.cxx"></File>
 				<File RelativePath="..\panda\src\testbed\test_map.cxx"></File>
 				<File RelativePath="..\panda\src\testbed\pgrid.cxx"></File>
 				<File RelativePath="..\panda\src\testbed\pgrid.cxx"></File>
 				<File RelativePath="..\panda\src\testbed\pview.cxx"></File>
 				<File RelativePath="..\panda\src\testbed\pview.cxx"></File>
-				<File RelativePath="..\panda\src\testbed\text_test.cxx"></File>
 			</Filter>
 			</Filter>
 			<Filter Name="cull">
 			<Filter Name="cull">
 				<File RelativePath="..\panda\src\cull\config_cull.cxx"></File>
 				<File RelativePath="..\panda\src\cull\config_cull.cxx"></File>

+ 4 - 1
makepanda/makewheel.py

@@ -488,7 +488,10 @@ def makewheel(version, output_dir, platform=default_platform):
 
 
     # Write the panda3d tree.  We use a custom empty __init__ since the
     # Write the panda3d tree.  We use a custom empty __init__ since the
     # default one adds the bin directory to the PATH, which we don't have.
     # default one adds the bin directory to the PATH, which we don't have.
-    whl.write_file_data('panda3d/__init__.py', '')
+    whl.write_file_data('panda3d/__init__.py', """"Python bindings for the Panda3D libraries"
+
+__version__ = '{0}'
+""".format(version))
 
 
     ext_suffix = GetExtensionSuffix()
     ext_suffix = GetExtensionSuffix()
 
 

+ 1 - 1
panda/src/chan/partBundle.h

@@ -172,7 +172,7 @@ private:
   typedef pvector<PartBundleNode *> Nodes;
   typedef pvector<PartBundleNode *> Nodes;
   Nodes _nodes;
   Nodes _nodes;
 
 
-  typedef pmap<WCPT(TransformState), WPT(PartBundle) > AppliedTransforms;
+  typedef pmap<WCPT(TransformState), WPT(PartBundle), std::owner_less<WCPT(TransformState)> > AppliedTransforms;
   AppliedTransforms _applied_transforms;
   AppliedTransforms _applied_transforms;
 
 
   double _update_delay;
   double _update_delay;

+ 5 - 1
panda/src/char/characterJointEffect.I

@@ -35,5 +35,9 @@ get_character() const {
  */
  */
 INLINE bool CharacterJointEffect::
 INLINE bool CharacterJointEffect::
 matches_character(Character *character) const {
 matches_character(Character *character) const {
-  return _character == character;
+  // This works because while the Character is destructing, the ref count will
+  // be 0 but was_deleted() will still return false.  We cannot construct a
+  // PointerTo to the character (via lock() or otherwise) when the reference
+  // count is 0 since that will cause double deletion.
+  return _character.get_orig() == character && !_character.was_deleted();
 }
 }

+ 145 - 0
panda/src/collide/collisionBox.cxx

@@ -545,6 +545,151 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
   return new_entry;
   return new_entry;
 }
 }
 
 
+/**
+ * Double dispatch point for tube as a FROM object
+ */
+PT(CollisionEntry) CollisionBox::
+test_intersection_from_tube(const CollisionEntry &entry) const {
+  const CollisionTube *tube;
+  DCAST_INTO_R(tube, entry.get_from(), nullptr);
+
+  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
+
+  LPoint3 from_a = tube->get_point_a() * wrt_mat;
+  LPoint3 from_b = tube->get_point_b() * wrt_mat;
+  LVector3 from_direction = from_b - from_a;
+  PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, tube->get_radius())).length_squared();
+  PN_stdfloat radius = csqrt(radius_sq);
+
+  LPoint3 box_min = get_min();
+  LPoint3 box_max = get_max();
+  LVector3 dimensions = box_max - box_min;
+
+  // The method below is inspired by Christer Ericson's book Real-Time
+  // Collision Detection.  Instead of testing a capsule against a box, we test
+  // a segment against an box that is oversized by the capsule radius.
+
+  // First, we test if the line segment intersects a box with its faces
+  // expanded outwards by the capsule radius.  If not, there is no collision.
+  double t1, t2;
+  if (!intersects_line(t1, t2, from_a, from_direction, radius)) {
+    return nullptr;
+  }
+
+  if (t2 < 0.0 || t1 > 1.0) {
+    return nullptr;
+  }
+
+  t1 = std::min(1.0, std::max(0.0, (t1 + t2) * 0.5));
+  LPoint3 point = from_a + from_direction * t1;
+
+  // We now have a point of intersection between the line segment and the
+  // oversized box.  Check on how many axes it lies outside the box.  If it is
+  // less than two, we know that it does not lie in one of the rounded regions
+  // of the oversized rounded box, and it is a guaranteed hit.  Otherwise, we
+  // will need to test against the edge regions.
+  if ((point[0] < box_min[0] || point[0] > box_max[0]) +
+      (point[1] < box_min[1] || point[1] > box_max[1]) +
+      (point[2] < box_min[2] || point[2] > box_max[2]) > 1) {
+    // Test the capsule against each edge of the box.
+    static const struct {
+      LPoint3 point;
+      int axis;
+    } edges[] = {
+      {{0, 0, 0}, 0},
+      {{0, 1, 0}, 0},
+      {{0, 0, 1}, 0},
+      {{0, 1, 1}, 0},
+      {{0, 0, 0}, 1},
+      {{0, 0, 1}, 1},
+      {{1, 0, 0}, 1},
+      {{1, 0, 1}, 1},
+      {{0, 0, 0}, 2},
+      {{0, 1, 0}, 2},
+      {{1, 0, 0}, 2},
+      {{1, 1, 0}, 2},
+    };
+
+    PN_stdfloat best_dist_sq = FLT_MAX;
+
+    for (int i = 0; i < 12; ++i) {
+      LPoint3 vertex = edges[i].point;
+      vertex.componentwise_mult(dimensions);
+      vertex += box_min;
+      LVector3 delta(0);
+      delta[edges[i].axis] = dimensions[edges[i].axis];
+      double u1, u2;
+      CollisionTube::calc_closest_segment_points(u1, u2, from_a, from_direction, vertex, delta);
+      PN_stdfloat dist_sq = ((from_a + from_direction * u1) - (vertex + delta * u2)).length_squared();
+      if (dist_sq < best_dist_sq) {
+        best_dist_sq = dist_sq;
+      }
+    }
+
+    if (best_dist_sq > radius_sq) {
+      // It is not actually touching any edge.
+      return nullptr;
+    }
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << entry.get_from_node_path()
+      << " into " << entry.get_into_node_path() << "\n";
+  }
+  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
+
+  // Which is the longest axis?
+  LVector3 diff = point - _center;
+  diff[0] /= dimensions[0];
+  diff[1] /= dimensions[1];
+  diff[2] /= dimensions[2];
+  int axis = 0;
+  if (cabs(diff[0]) > cabs(diff[1])) {
+    if (cabs(diff[0]) > cabs(diff[2])) {
+      axis = 0;
+    } else {
+      axis = 2;
+    }
+  } else {
+    if (cabs(diff[1]) > cabs(diff[2])) {
+      axis = 1;
+    } else {
+      axis = 2;
+    }
+  }
+  LVector3 normal(0);
+  normal[axis] = std::copysign(1, diff[axis]);
+
+  LPoint3 clamped = point.fmax(box_min).fmin(box_max);
+  LPoint3 surface_point = clamped;
+  surface_point[axis] = (diff[axis] >= 0.0f) ? box_max[axis] : box_min[axis];
+
+  // Is the point inside the box?
+  LVector3 interior_vec;
+  if (clamped != point) {
+    // No, it is outside.  The interior point is in the direction of the
+    // surface point.
+    interior_vec = point - surface_point;
+    if (!interior_vec.normalize()) {
+      interior_vec = normal;
+    }
+  } else {
+    // It is inside.  I think any point will work for this.
+    interior_vec = normal;
+  }
+  new_entry->set_interior_point(point - interior_vec * radius);
+  new_entry->set_surface_point(surface_point);
+
+  if (has_effective_normal() && tube->get_respect_effective_normal()) {
+    new_entry->set_surface_normal(get_effective_normal());
+  } else {
+    new_entry->set_surface_normal(normal);
+  }
+
+  return new_entry;
+}
+
 /**
 /**
  * Double dispatch point for box as a FROM object
  * Double dispatch point for box as a FROM object
  */
  */

+ 2 - 0
panda/src/collide/collisionBox.h

@@ -81,6 +81,8 @@ protected:
     test_intersection_from_ray(const CollisionEntry &entry) const;
     test_intersection_from_ray(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
   virtual PT(CollisionEntry)
     test_intersection_from_segment(const CollisionEntry &entry) const;
     test_intersection_from_segment(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+    test_intersection_from_tube(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
   virtual PT(CollisionEntry)
     test_intersection_from_box(const CollisionEntry &entry) const;
     test_intersection_from_box(const CollisionEntry &entry) const;
 
 

+ 2 - 0
panda/src/collide/collisionTube.h

@@ -151,6 +151,8 @@ public:
 
 
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
+
+  friend class CollisionBox;
 };
 };
 
 
 #include "collisionTube.I"
 #include "collisionTube.I"

+ 0 - 70
panda/src/collide/test_collide.cxx

@@ -1,70 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file test_collide.cxx
- * @author drose
- * @date 2000-04-24
- */
-
-#include "collisionTraverser.h"
-#include "collisionNode.h"
-#include "collisionSphere.h"
-#include "collisionPlane.h"
-#include "collisionHandlerPusher.h"
-
-#include "namedNode.h"
-#include "pt_NamedNode.h"
-#include "pointerTo.h"
-#include "transformTransition.h"
-#include "luse.h"
-#include "get_rel_pos.h"
-#include "renderRelation.h"
-
-int
-main(int argc, char *argv[]) {
-  PT_NamedNode r = new NamedNode("r");
-
-  PT_NamedNode a = new NamedNode("a");
-  PT_NamedNode b = new NamedNode("b");
-
-  PT(CollisionNode) aa = new CollisionNode("aa");
-  PT(CollisionNode) ab = new CollisionNode("ab");
-  PT(CollisionNode) ba = new CollisionNode("ba");
-
-  RenderRelation *r_a = new RenderRelation(r, a);
-  RenderRelation *r_b = new RenderRelation(r, b);
-
-  RenderRelation *a_aa = new RenderRelation(a, aa);
-  RenderRelation *a_ab = new RenderRelation(a, ab);
-  RenderRelation *b_ba = new RenderRelation(b, ba);
-
-
-  CollisionSphere *aa1 = new CollisionSphere(LPoint3f(0, 0, 0), 1);
-  aa->add_solid(aa1);
-  a_aa->set_transition(new TransformTransition(LMatrix4f::translate_mat(0, -5, 0)));
-
-  CollisionSphere *ab1 = new CollisionSphere(LPoint3f(0, 2, 0), 1.5);
-  ab->add_solid(ab1);
-
-  Planef plane(LVector3f(0, 1, 0), LPoint3f(0, 0, 0));
-  CollisionPlane *ba1 = new CollisionPlane(plane);
-  ba->add_solid(ba1);
-
-  CollisionTraverser ct;
-  PT(CollisionHandlerPusher) chp = new CollisionHandlerPusher;
-  chp->add_collider(aa, a_aa);
-  ct.add_collider(aa, chp);
-
-  ct.traverse(r);
-
-  nout << "\nFrame 2:\n\n";
-
-  ct.traverse(r);
-
-  return (0);
-}

+ 3 - 5
panda/src/cull/cullBinBackToFront.cxx

@@ -85,9 +85,6 @@ void CullBinBackToFront::
 draw(bool force, Thread *current_thread) {
 draw(bool force, Thread *current_thread) {
   PStatTimer timer(_draw_this_pcollector, current_thread);
   PStatTimer timer(_draw_this_pcollector, current_thread);
 
 
-  GeomPipelineReader geom_reader(current_thread);
-  GeomVertexDataPipelineReader data_reader(current_thread);
-
   Objects::const_iterator oi;
   Objects::const_iterator oi;
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
     CullableObject *object = (*oi)._object;
     CullableObject *object = (*oi)._object;
@@ -96,9 +93,10 @@ draw(bool force, Thread *current_thread) {
       nassertd(object->_geom != nullptr) continue;
       nassertd(object->_geom != nullptr) continue;
 
 
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
-      data_reader.set_object(object->_munged_data);
+
+      GeomPipelineReader geom_reader(object->_geom, current_thread);
+      GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
       data_reader.check_array_readers();
       data_reader.check_array_readers();
-      geom_reader.set_object(object->_geom);
       geom_reader.draw(_gsg, &data_reader, force);
       geom_reader.draw(_gsg, &data_reader, force);
     } else {
     } else {
       // It has a callback associated.
       // It has a callback associated.

+ 3 - 5
panda/src/cull/cullBinFixed.cxx

@@ -71,9 +71,6 @@ void CullBinFixed::
 draw(bool force, Thread *current_thread) {
 draw(bool force, Thread *current_thread) {
   PStatTimer timer(_draw_this_pcollector, current_thread);
   PStatTimer timer(_draw_this_pcollector, current_thread);
 
 
-  GeomPipelineReader geom_reader(current_thread);
-  GeomVertexDataPipelineReader data_reader(current_thread);
-
   Objects::const_iterator oi;
   Objects::const_iterator oi;
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
     CullableObject *object = (*oi)._object;
     CullableObject *object = (*oi)._object;
@@ -82,9 +79,10 @@ draw(bool force, Thread *current_thread) {
       nassertd(object->_geom != nullptr) continue;
       nassertd(object->_geom != nullptr) continue;
 
 
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
-      data_reader.set_object(object->_munged_data);
+
+      GeomPipelineReader geom_reader(object->_geom, current_thread);
+      GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
       data_reader.check_array_readers();
       data_reader.check_array_readers();
-      geom_reader.set_object(object->_geom);
       geom_reader.draw(_gsg, &data_reader, force);
       geom_reader.draw(_gsg, &data_reader, force);
     } else {
     } else {
       // It has a callback associated.
       // It has a callback associated.

+ 3 - 5
panda/src/cull/cullBinFrontToBack.cxx

@@ -85,9 +85,6 @@ void CullBinFrontToBack::
 draw(bool force, Thread *current_thread) {
 draw(bool force, Thread *current_thread) {
   PStatTimer timer(_draw_this_pcollector, current_thread);
   PStatTimer timer(_draw_this_pcollector, current_thread);
 
 
-  GeomPipelineReader geom_reader(current_thread);
-  GeomVertexDataPipelineReader data_reader(current_thread);
-
   Objects::const_iterator oi;
   Objects::const_iterator oi;
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
     CullableObject *object = (*oi)._object;
     CullableObject *object = (*oi)._object;
@@ -96,9 +93,10 @@ draw(bool force, Thread *current_thread) {
       nassertd(object->_geom != nullptr) continue;
       nassertd(object->_geom != nullptr) continue;
 
 
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
-      data_reader.set_object(object->_munged_data);
+
+      GeomPipelineReader geom_reader(object->_geom, current_thread);
+      GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
       data_reader.check_array_readers();
       data_reader.check_array_readers();
-      geom_reader.set_object(object->_geom);
       geom_reader.draw(_gsg, &data_reader, force);
       geom_reader.draw(_gsg, &data_reader, force);
     } else {
     } else {
       // It has a callback associated.
       // It has a callback associated.

+ 3 - 5
panda/src/cull/cullBinStateSorted.cxx

@@ -70,9 +70,6 @@ void CullBinStateSorted::
 draw(bool force, Thread *current_thread) {
 draw(bool force, Thread *current_thread) {
   PStatTimer timer(_draw_this_pcollector, current_thread);
   PStatTimer timer(_draw_this_pcollector, current_thread);
 
 
-  GeomPipelineReader geom_reader(current_thread);
-  GeomVertexDataPipelineReader data_reader(current_thread);
-
   Objects::const_iterator oi;
   Objects::const_iterator oi;
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
     CullableObject *object = (*oi)._object;
     CullableObject *object = (*oi)._object;
@@ -81,9 +78,10 @@ draw(bool force, Thread *current_thread) {
       nassertd(object->_geom != nullptr) continue;
       nassertd(object->_geom != nullptr) continue;
 
 
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
-      data_reader.set_object(object->_munged_data);
+
+      GeomPipelineReader geom_reader(object->_geom, current_thread);
+      GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
       data_reader.check_array_readers();
       data_reader.check_array_readers();
-      geom_reader.set_object(object->_geom);
       geom_reader.draw(_gsg, &data_reader, force);
       geom_reader.draw(_gsg, &data_reader, force);
     } else {
     } else {
       // It has a callback associated.
       // It has a callback associated.

+ 3 - 5
panda/src/cull/cullBinUnsorted.cxx

@@ -55,9 +55,6 @@ void CullBinUnsorted::
 draw(bool force, Thread *current_thread) {
 draw(bool force, Thread *current_thread) {
   PStatTimer timer(_draw_this_pcollector, current_thread);
   PStatTimer timer(_draw_this_pcollector, current_thread);
 
 
-  GeomPipelineReader geom_reader(current_thread);
-  GeomVertexDataPipelineReader data_reader(current_thread);
-
   Objects::iterator oi;
   Objects::iterator oi;
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
   for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
     CullableObject *object = (*oi);
     CullableObject *object = (*oi);
@@ -66,9 +63,10 @@ draw(bool force, Thread *current_thread) {
       nassertd(object->_geom != nullptr) continue;
       nassertd(object->_geom != nullptr) continue;
 
 
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
       _gsg->set_state_and_transform(object->_state, object->_internal_transform);
-      data_reader.set_object(object->_munged_data);
+
+      GeomPipelineReader geom_reader(object->_geom, current_thread);
+      GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
       data_reader.check_array_readers();
       data_reader.check_array_readers();
-      geom_reader.set_object(object->_geom);
       geom_reader.draw(_gsg, &data_reader, force);
       geom_reader.draw(_gsg, &data_reader, force);
     } else {
     } else {
       // It has a callback associated.
       // It has a callback associated.

+ 10 - 4
panda/src/dxgsg9/dxGeomMunger9.cxx

@@ -300,8 +300,11 @@ compare_to_impl(const GeomMunger *other) const {
   if (_filtered_texture != om->_filtered_texture) {
   if (_filtered_texture != om->_filtered_texture) {
     return _filtered_texture < om->_filtered_texture ? -1 : 1;
     return _filtered_texture < om->_filtered_texture ? -1 : 1;
   }
   }
-  if (_tex_gen != om->_tex_gen) {
-    return _tex_gen < om->_tex_gen ? -1 : 1;
+  if (_tex_gen.owner_before(om->_tex_gen)) {
+    return -1;
+  }
+  if (om->_tex_gen.owner_before(_tex_gen)) {
+    return 1;
   }
   }
 
 
   return StandardMunger::compare_to_impl(other);
   return StandardMunger::compare_to_impl(other);
@@ -321,8 +324,11 @@ geom_compare_to_impl(const GeomMunger *other) const {
   if (_filtered_texture != om->_filtered_texture) {
   if (_filtered_texture != om->_filtered_texture) {
     return _filtered_texture < om->_filtered_texture ? -1 : 1;
     return _filtered_texture < om->_filtered_texture ? -1 : 1;
   }
   }
-  if (_tex_gen != om->_tex_gen) {
-    return _tex_gen < om->_tex_gen ? -1 : 1;
+  if (_tex_gen.owner_before(om->_tex_gen)) {
+    return -1;
+  }
+  if (om->_tex_gen.owner_before(_tex_gen)) {
+    return 1;
   }
   }
 
 
   return StandardMunger::geom_compare_to_impl(other);
   return StandardMunger::geom_compare_to_impl(other);

+ 20 - 0
panda/src/express/pointerTo.h

@@ -215,6 +215,26 @@ void swap(ConstPointerTo<T> &one, ConstPointerTo<T> &two) noexcept {
 }
 }
 
 
 
 
+// Define owner_less specializations, for completeness' sake.
+namespace std {
+  template<class T>
+  struct owner_less<PointerTo<T> > {
+    bool operator () (const PointerTo<T> &lhs,
+                      const PointerTo<T> &rhs) const noexcept {
+      return lhs < rhs;
+    }
+  };
+
+  template<class T>
+  struct owner_less<ConstPointerTo<T> > {
+    bool operator () (const ConstPointerTo<T> &lhs,
+                      const ConstPointerTo<T> &rhs) const noexcept {
+      return lhs < rhs;
+    }
+  };
+}
+
+
 // Finally, we'll define a couple of handy abbreviations to save on all that
 // Finally, we'll define a couple of handy abbreviations to save on all that
 // wasted typing time.
 // wasted typing time.
 
 

+ 1 - 0
panda/src/express/pointerToBase.h

@@ -53,6 +53,7 @@ protected:
 
 
   // This is needed to be able to access the privates of other instantiations.
   // This is needed to be able to access the privates of other instantiations.
   template<typename Y> friend class PointerToBase;
   template<typename Y> friend class PointerToBase;
+  template<typename Y> friend class WeakPointerToBase;
 
 
 PUBLISHED:
 PUBLISHED:
   ALWAYS_INLINE void clear();
   ALWAYS_INLINE void clear();

+ 0 - 7
panda/src/express/pointerToVoid.I

@@ -11,13 +11,6 @@
  * @date 2004-09-27
  * @date 2004-09-27
  */
  */
 
 
-/**
- *
- */
-constexpr PointerToVoid::
-PointerToVoid() noexcept : _void_ptr(nullptr) {
-}
-
 /**
 /**
  *
  *
  */
  */

+ 2 - 2
panda/src/express/pointerToVoid.h

@@ -32,7 +32,7 @@
  */
  */
 class EXPCL_PANDA_EXPRESS PointerToVoid : public MemoryBase {
 class EXPCL_PANDA_EXPRESS PointerToVoid : public MemoryBase {
 protected:
 protected:
-  constexpr PointerToVoid() noexcept;
+  constexpr PointerToVoid() noexcept = default;
   //INLINE ~PointerToVoid();
   //INLINE ~PointerToVoid();
 
 
 private:
 private:
@@ -63,7 +63,7 @@ protected:
   // a PointerTo any class that inherits virtually from ReferenceCount.  (You
   // a PointerTo any class that inherits virtually from ReferenceCount.  (You
   // can't downcast past a virtual inheritance level, but you can always
   // can't downcast past a virtual inheritance level, but you can always
   // cross-cast from a void pointer.)
   // cross-cast from a void pointer.)
-  AtomicAdjust::Pointer _void_ptr;
+  AtomicAdjust::Pointer _void_ptr = nullptr;
 };
 };
 
 
 #include "pointerToVoid.I"
 #include "pointerToVoid.I"

+ 264 - 34
panda/src/express/weakPointerTo.I

@@ -39,6 +39,49 @@ WeakPointerTo(const WeakPointerTo<T> &copy) :
 {
 {
 }
 }
 
 
+/**
+ *
+ */
+template<class T>
+INLINE WeakPointerTo<T>::
+WeakPointerTo(WeakPointerTo<T> &&from) noexcept :
+  WeakPointerToBase<T>(std::move(from))
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T>::
+WeakPointerTo(const WeakPointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T>::
+WeakPointerTo(const PointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T>::
+WeakPointerTo(WeakPointerTo<Y> &&r) noexcept :
+  WeakPointerToBase<T>(std::move(r))
+{
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -82,23 +125,9 @@ operator T * () const {
 template<class T>
 template<class T>
 INLINE PointerTo<T> WeakPointerTo<T>::
 INLINE PointerTo<T> WeakPointerTo<T>::
 lock() const {
 lock() const {
-  WeakReferenceList *weak_ref = this->_weak_ref;
-  if (weak_ref != nullptr) {
-    PointerTo<T> ptr;
-    weak_ref->_lock.lock();
-    if (!weak_ref->was_deleted()) {
-      // We also need to check that the reference count is not zero (which can
-      // happen if the object is currently being destructed), since that could
-      // cause double deletion.
-      To *plain_ptr = (To *)WeakPointerToBase<T>::_void_ptr;
-      if (plain_ptr != nullptr && plain_ptr->ref_if_nonzero()) {
-        ptr.cheat() = plain_ptr;
-      }
-    }
-    weak_ref->_lock.unlock();
-    return ptr;
-  }
-  return nullptr;
+  PointerTo<T> ptr;
+  this->lock_into(ptr);
+  return ptr;
 }
 }
 
 
 /**
 /**
@@ -152,6 +181,49 @@ operator = (const WeakPointerTo<T> &copy) {
   return *this;
   return *this;
 }
 }
 
 
+/**
+ *
+ */
+template<class T>
+INLINE WeakPointerTo<T> &WeakPointerTo<T>::
+operator = (WeakPointerTo<T> &&from) noexcept {
+  this->reassign(std::move(from));
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T> &WeakPointerTo<T>::
+operator = (const WeakPointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T> &WeakPointerTo<T>::
+operator = (const PointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakPointerTo<T> &WeakPointerTo<T>::
+operator = (WeakPointerTo<Y> &&r) noexcept {
+  this->reassign(std::move(r));
+  return *this;
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -202,6 +274,92 @@ WeakConstPointerTo(const WeakConstPointerTo<T> &copy) :
 {
 {
 }
 }
 
 
+/**
+ *
+ */
+template<class T>
+INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(WeakPointerTo<T> &&from) noexcept :
+  WeakPointerToBase<T>(std::move(from))
+{
+}
+
+/**
+ *
+ */
+template<class T>
+INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(WeakConstPointerTo<T> &&from) noexcept :
+  WeakPointerToBase<T>(std::move(from))
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(const WeakPointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(const WeakConstPointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(const PointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(const ConstPointerTo<Y> &r) noexcept :
+  WeakPointerToBase<T>(r)
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(WeakPointerTo<Y> &&r) noexcept :
+  WeakPointerToBase<T>(std::move(r))
+{
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T>::
+WeakConstPointerTo(WeakConstPointerTo<Y> &&r) noexcept :
+  WeakPointerToBase<T>(std::move(r))
+{
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -243,23 +401,9 @@ operator const T * () const {
 template<class T>
 template<class T>
 INLINE ConstPointerTo<T> WeakConstPointerTo<T>::
 INLINE ConstPointerTo<T> WeakConstPointerTo<T>::
 lock() const {
 lock() const {
-  WeakReferenceList *weak_ref = this->_weak_ref;
-  if (weak_ref != nullptr) {
-    ConstPointerTo<T> ptr;
-    weak_ref->_lock.lock();
-    if (!weak_ref->was_deleted()) {
-      // We also need to check that the reference count is not zero (which can
-      // happen if the object is currently being destructed), since that could
-      // cause double deletion.
-      const To *plain_ptr = (const To *)WeakPointerToBase<T>::_void_ptr;
-      if (plain_ptr != nullptr && plain_ptr->ref_if_nonzero()) {
-        ptr.cheat() = plain_ptr;
-      }
-    }
-    weak_ref->_lock.unlock();
-    return ptr;
-  }
-  return nullptr;
+  ConstPointerTo<T> ptr;
+  this->lock_into(ptr);
+  return ptr;
 }
 }
 
 
 /**
 /**
@@ -332,3 +476,89 @@ operator = (const WeakConstPointerTo<T> &copy) {
   ((WeakConstPointerTo<T> *)this)->reassign(*(const PointerToBase<T> *)&copy);
   ((WeakConstPointerTo<T> *)this)->reassign(*(const PointerToBase<T> *)&copy);
   return *this;
   return *this;
 }
 }
+
+/**
+ *
+ */
+template<class T>
+INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (WeakPointerTo<T> &&from) noexcept {
+  this->reassign(std::move(from));
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (WeakConstPointerTo<T> &&from) noexcept {
+  this->reassign(std::move(from));
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (const WeakPointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (const WeakConstPointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (const PointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (const ConstPointerTo<Y> &r) noexcept {
+  this->reassign(r);
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (WeakPointerTo<Y> &&r) noexcept {
+  this->reassign(std::move(r));
+  return *this;
+}
+
+/**
+ *
+ */
+template<class T>
+template<class Y>
+ALWAYS_INLINE WeakConstPointerTo<T> &WeakConstPointerTo<T>::
+operator = (WeakConstPointerTo<Y> &&r) noexcept {
+  this->reassign(std::move(r));
+  return *this;
+}

+ 77 - 2
panda/src/express/weakPointerTo.h

@@ -30,11 +30,21 @@ class WeakPointerTo : public WeakPointerToBase<T> {
 public:
 public:
   typedef typename WeakPointerToBase<T>::To To;
   typedef typename WeakPointerToBase<T>::To To;
 PUBLISHED:
 PUBLISHED:
-  INLINE WeakPointerTo(To *ptr = nullptr);
+  constexpr WeakPointerTo() noexcept = default;
+  INLINE WeakPointerTo(To *ptr);
   INLINE WeakPointerTo(const PointerTo<T> &copy);
   INLINE WeakPointerTo(const PointerTo<T> &copy);
   INLINE WeakPointerTo(const WeakPointerTo<T> &copy);
   INLINE WeakPointerTo(const WeakPointerTo<T> &copy);
 
 
 public:
 public:
+  INLINE WeakPointerTo(WeakPointerTo<T> &&from) noexcept;
+
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo(const WeakPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo(const PointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo(WeakPointerTo<Y> &&r) noexcept;
+
   INLINE To &operator *() const;
   INLINE To &operator *() const;
   INLINE To *operator -> () const;
   INLINE To *operator -> () const;
   // MSVC.NET 2005 insists that we use T *, and not To *, here.
   // MSVC.NET 2005 insists that we use T *, and not To *, here.
@@ -49,6 +59,17 @@ PUBLISHED:
   INLINE WeakPointerTo<T> &operator = (const PointerTo<T> &copy);
   INLINE WeakPointerTo<T> &operator = (const PointerTo<T> &copy);
   INLINE WeakPointerTo<T> &operator = (const WeakPointerTo<T> &copy);
   INLINE WeakPointerTo<T> &operator = (const WeakPointerTo<T> &copy);
 
 
+public:
+  INLINE WeakPointerTo<T> &operator = (WeakPointerTo<T> &&from) noexcept;
+
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo<T> &operator = (const WeakPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo<T> &operator = (const PointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakPointerTo<T> &operator = (WeakPointerTo<Y> &&r) noexcept;
+
+PUBLISHED:
   // This function normally wouldn't need to be redefined here, but we do so
   // This function normally wouldn't need to be redefined here, but we do so
   // anyway just to help out interrogate (which doesn't seem to want to
   // anyway just to help out interrogate (which doesn't seem to want to
   // automatically export the WeakPointerToBase class).  When this works again
   // automatically export the WeakPointerToBase class).  When this works again
@@ -66,13 +87,30 @@ class WeakConstPointerTo : public WeakPointerToBase<T> {
 public:
 public:
   typedef typename WeakPointerToBase<T>::To To;
   typedef typename WeakPointerToBase<T>::To To;
 PUBLISHED:
 PUBLISHED:
-  INLINE WeakConstPointerTo(const To *ptr = nullptr);
+  constexpr WeakConstPointerTo() noexcept = default;
+  INLINE WeakConstPointerTo(const To *ptr);
   INLINE WeakConstPointerTo(const PointerTo<T> &copy);
   INLINE WeakConstPointerTo(const PointerTo<T> &copy);
   INLINE WeakConstPointerTo(const ConstPointerTo<T> &copy);
   INLINE WeakConstPointerTo(const ConstPointerTo<T> &copy);
   INLINE WeakConstPointerTo(const WeakPointerTo<T> &copy);
   INLINE WeakConstPointerTo(const WeakPointerTo<T> &copy);
   INLINE WeakConstPointerTo(const WeakConstPointerTo<T> &copy);
   INLINE WeakConstPointerTo(const WeakConstPointerTo<T> &copy);
 
 
 public:
 public:
+  INLINE WeakConstPointerTo(WeakPointerTo<T> &&from) noexcept;
+  INLINE WeakConstPointerTo(WeakConstPointerTo<T> &&from) noexcept;
+
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(const WeakPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(const WeakConstPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(const PointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(const ConstPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(WeakPointerTo<Y> &&r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo(WeakConstPointerTo<Y> &&r) noexcept;
+
   INLINE const To &operator *() const;
   INLINE const To &operator *() const;
   INLINE const To *operator -> () const;
   INLINE const To *operator -> () const;
   INLINE explicit operator const T *() const;
   INLINE explicit operator const T *() const;
@@ -88,6 +126,24 @@ PUBLISHED:
   INLINE WeakConstPointerTo<T> &operator = (const WeakPointerTo<T> &copy);
   INLINE WeakConstPointerTo<T> &operator = (const WeakPointerTo<T> &copy);
   INLINE WeakConstPointerTo<T> &operator = (const WeakConstPointerTo<T> &copy);
   INLINE WeakConstPointerTo<T> &operator = (const WeakConstPointerTo<T> &copy);
 
 
+public:
+  INLINE WeakConstPointerTo<T> &operator = (WeakPointerTo<T> &&from) noexcept;
+  INLINE WeakConstPointerTo<T> &operator = (WeakConstPointerTo<T> &&from) noexcept;
+
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (const WeakPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (const WeakConstPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (const PointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (const ConstPointerTo<Y> &r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (WeakPointerTo<Y> &&r) noexcept;
+  template<class Y>
+  ALWAYS_INLINE WeakConstPointerTo<T> &operator = (WeakConstPointerTo<Y> &&r) noexcept;
+
+PUBLISHED:
   // These functions normally wouldn't need to be redefined here, but we do so
   // These functions normally wouldn't need to be redefined here, but we do so
   // anyway just to help out interrogate (which doesn't seem to want to
   // anyway just to help out interrogate (which doesn't seem to want to
   // automatically export the WeakPointerToBase class).  When this works again
   // automatically export the WeakPointerToBase class).  When this works again
@@ -96,6 +152,25 @@ PUBLISHED:
   INLINE void clear() { WeakPointerToBase<T>::clear(); }
   INLINE void clear() { WeakPointerToBase<T>::clear(); }
 };
 };
 
 
+// Provide specializations of std::owner_less, for using a WPT as a map key.
+namespace std {
+  template<class T>
+  struct owner_less<WeakPointerTo<T> > {
+    bool operator () (const WeakPointerTo<T> &lhs,
+                      const WeakPointerTo<T> &rhs) const noexcept {
+      return lhs.owner_before(rhs);
+    }
+  };
+
+  template<class T>
+  struct owner_less<WeakConstPointerTo<T> > {
+    bool operator () (const WeakConstPointerTo<T> &lhs,
+                      const WeakConstPointerTo<T> &rhs) const noexcept {
+      return lhs.owner_before(rhs);
+    }
+  };
+}
+
 #define WPT(type) WeakPointerTo< type >
 #define WPT(type) WeakPointerTo< type >
 #define WCPT(type) WeakConstPointerTo< type >
 #define WCPT(type) WeakConstPointerTo< type >
 
 

+ 191 - 33
panda/src/express/weakPointerToBase.I

@@ -12,7 +12,8 @@
  */
  */
 
 
 /**
 /**
- *
+ * Constructs a weak pointer from a plain pointer (or nullptr).  It is the
+ * caller's responsibility to ensure that it points to a valid object.
  */
  */
 template<class T>
 template<class T>
 INLINE WeakPointerToBase<T>::
 INLINE WeakPointerToBase<T>::
@@ -27,7 +28,7 @@ WeakPointerToBase(To *ptr) {
 }
 }
 
 
 /**
 /**
- *
+ * Constructs a weak pointer from a reference-counting pointer.
  */
  */
 template<class T>
 template<class T>
 INLINE WeakPointerToBase<T>::
 INLINE WeakPointerToBase<T>::
@@ -42,44 +43,72 @@ WeakPointerToBase(const PointerToBase<T> &copy) {
 }
 }
 
 
 /**
 /**
- *
+ * Copies a weak pointer.  This is always safe, even for expired pointers.
  */
  */
 template<class T>
 template<class T>
 INLINE WeakPointerToBase<T>::
 INLINE WeakPointerToBase<T>::
 WeakPointerToBase(const WeakPointerToBase<T> &copy) {
 WeakPointerToBase(const WeakPointerToBase<T> &copy) {
   _void_ptr = copy._void_ptr;
   _void_ptr = copy._void_ptr;
 
 
-  // Don't bother increasing the weak reference count if the object was
-  // already deleted.
+  // While it is tempting to stop maintaining the control block pointer after
+  // the object has been deleted, we still need it in order to define a
+  // consistent ordering in owner_before.
   WeakReferenceList *weak_ref = copy._weak_ref;
   WeakReferenceList *weak_ref = copy._weak_ref;
-  if (weak_ref != nullptr && !weak_ref->was_deleted()) {
+  if (weak_ref != nullptr/* && !weak_ref->was_deleted()*/) {
     _weak_ref = copy._weak_ref;
     _weak_ref = copy._weak_ref;
     _weak_ref->ref();
     _weak_ref->ref();
   }
   }
 }
 }
 
 
 /**
 /**
- *
+ * Moves a weak pointer.  This is always safe, even for expired pointers.
  */
  */
 template<class T>
 template<class T>
 INLINE WeakPointerToBase<T>::
 INLINE WeakPointerToBase<T>::
 WeakPointerToBase(WeakPointerToBase<T> &&from) noexcept {
 WeakPointerToBase(WeakPointerToBase<T> &&from) noexcept {
-  // Protect against self-move-assignment.
-  if (from._void_ptr != this->_void_ptr) {
-    WeakReferenceList *old_ref = (To *)this->_weak_ref;
+  this->_void_ptr = from._void_ptr;
+  this->_weak_ref = from._weak_ref;
+  from._void_ptr = nullptr;
+  from._weak_ref = nullptr;
+}
 
 
-    this->_void_ptr = from._void_ptr;
-    this->_weak_ref = from._weak_ref;
-    from._void_ptr = nullptr;
-    from._weak_ref = nullptr;
+/**
+ * Copies a weak pointer from a cast-convertible weak pointer type.
+ */
+template<class T>
+template<class Y>
+INLINE WeakPointerToBase<T>::
+WeakPointerToBase(const WeakPointerToBase<Y> &r) {
+  // If this next line gives an error, you are trying to convert a WeakPointerTo
+  // from an incompatible type of another WeakPointerTo.
+  To *ptr = (Y *)r._void_ptr;
 
 
-    // Now delete the old pointer.
-    if (old_ref != nullptr && !old_ref->unref()) {
-      delete old_ref;
-    }
+  this->_void_ptr = ptr;
+
+  WeakReferenceList *weak_ref = r._weak_ref;
+  if (weak_ref != nullptr) {
+    _weak_ref = weak_ref;
+    weak_ref->ref();
   }
   }
 }
 }
 
 
+/**
+ * Moves a weak pointer from a cast-convertible weak pointer type.
+ */
+template<class T>
+template<class Y>
+INLINE WeakPointerToBase<T>::
+WeakPointerToBase(WeakPointerToBase<Y> &&r) noexcept {
+  // If this next line gives an error, you are trying to convert a WeakPointerTo
+  // from an incompatible type of another WeakPointerTo.
+  To *ptr = (Y *)r._void_ptr;
+
+  this->_void_ptr = ptr;
+  this->_weak_ref = r._weak_ref;
+  r._void_ptr = nullptr;
+  r._weak_ref = nullptr;
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -141,10 +170,11 @@ reassign(const WeakPointerToBase<To> &copy) {
     WeakReferenceList *old_ref = (WeakReferenceList *)_weak_ref;
     WeakReferenceList *old_ref = (WeakReferenceList *)_weak_ref;
     _void_ptr = new_ptr;
     _void_ptr = new_ptr;
 
 
-    // Don't bother increasing the weak reference count if the object was
-    // already deleted.
+    // While it is tempting to stop maintaining the control block pointer
+    // after the object has been deleted, we still need it in order to define
+    // a consistent ordering in owner_before.
     WeakReferenceList *weak_ref = copy._weak_ref;
     WeakReferenceList *weak_ref = copy._weak_ref;
-    if (weak_ref != nullptr && !weak_ref->was_deleted()) {
+    if (weak_ref != nullptr/* && !weak_ref->was_deleted()*/) {
       weak_ref->ref();
       weak_ref->ref();
       _weak_ref = weak_ref;
       _weak_ref = weak_ref;
     } else {
     } else {
@@ -180,6 +210,61 @@ reassign(WeakPointerToBase<To> &&from) noexcept {
   }
   }
 }
 }
 
 
+/**
+ * Like above, but casts from a compatible pointer type.
+ */
+template<class T>
+template<class Y>
+INLINE void WeakPointerToBase<T>::
+reassign(const WeakPointerToBase<Y> &copy) {
+  // If there is a compile error on this line, it means you tried to assign
+  // an incompatible type.
+  To *new_ptr = (Y *)copy._void_ptr;
+
+  if (new_ptr != (To *)_void_ptr) {
+    WeakReferenceList *old_ref = (WeakReferenceList *)_weak_ref;
+    WeakReferenceList *new_ref = copy._weak_ref;
+    _void_ptr = new_ptr;
+    _weak_ref = new_ref;
+
+    if (new_ref != nullptr) {
+      new_ref->ref();
+    }
+
+    // Now remove the old reference.
+    if (old_ref != nullptr && !old_ref->unref()) {
+      delete old_ref;
+    }
+  }
+}
+
+/**
+ * Like above, but casts from a compatible pointer type.
+ */
+template<class T>
+template<class Y>
+INLINE void WeakPointerToBase<T>::
+reassign(WeakPointerToBase<Y> &&from) noexcept {
+  // Protect against self-move-assignment.
+  if (from._void_ptr != this->_void_ptr) {
+    WeakReferenceList *old_ref = (WeakReferenceList *)this->_weak_ref;
+
+    // If there is a compile error on this line, it means you tried to assign
+    // an incompatible type.
+    To *new_ptr = (Y *)from._void_ptr;
+
+    this->_void_ptr = new_ptr;
+    this->_weak_ref = from._weak_ref;
+    from._void_ptr = nullptr;
+    from._weak_ref = nullptr;
+
+    // Now delete the old pointer.
+    if (old_ref != nullptr && !old_ref->unref()) {
+      delete old_ref;
+    }
+  }
+}
+
 /**
 /**
  * Ensures that the MemoryUsage record for the pointer has the right type of
  * Ensures that the MemoryUsage record for the pointer has the right type of
  * object, if we know the type ourselves.
  * object, if we know the type ourselves.
@@ -201,6 +286,34 @@ update_type(To *ptr) {
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 }
 }
 
 
+/**
+ * A thread-safe way to access the underlying pointer; will only write to the
+ * given pointer if the underlying pointer has not yet been deleted and is not
+ * null.  Note that it may leave the pointer unassigned even if was_deleted()
+ * still returns true, which can occur if the object has reached reference
+ * count 0 and is about to be destroyed.
+ */
+template<class T>
+INLINE void WeakPointerToBase<T>::
+lock_into(PointerToBase<To> &locked) const {
+  WeakReferenceList *weak_ref = this->_weak_ref;
+  if (weak_ref != nullptr) {
+    weak_ref->_lock.lock();
+    if (!weak_ref->was_deleted()) {
+      // We also need to check that the reference count is not zero (which can
+      // happen if the object is currently being destructed), since that could
+      // cause double deletion.
+      To *plain_ptr = (To *)WeakPointerToBase<T>::_void_ptr;
+      if (plain_ptr != nullptr && plain_ptr->ref_if_nonzero()) {
+        // It is valid and we successfully grabbed a reference.  Assign it,
+        // noting we have already incremented the reference count.
+        locked._void_ptr = plain_ptr;
+      }
+    }
+    weak_ref->_lock.unlock();
+  }
+}
+
 #ifndef CPPPARSER
 #ifndef CPPPARSER
 /**
 /**
  *
  *
@@ -337,7 +450,11 @@ operator >= (std::nullptr_t) const {
 }
 }
 
 
 /**
 /**
- *
+ * Returns true if both pointers have the same raw pointer value.  For this to
+ * be meaningful, neither pointer may have expired, since if one has expired
+ * while the other was allocated at the expired pointer's memory address, this
+ * comparison will be true even though they didn't refer to the same object.
+ * @see owner_before
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -346,7 +463,7 @@ operator == (const WeakPointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * @see operator ==
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -355,7 +472,8 @@ operator != (const WeakPointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * Defines an ordering between WeakPointerTo based on their raw pointer value.
+ * @deprecated Do not use this.  Use owner_before or std::owner_less instead.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -364,7 +482,8 @@ operator > (const WeakPointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * Defines an ordering between WeakPointerTo based on their raw pointer value.
+ * @deprecated Do not use this.  Use owner_before or std::owner_less instead.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -373,7 +492,8 @@ operator <= (const WeakPointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * Defines an ordering between WeakPointerTo based on their raw pointer value.
+ * @deprecated Do not use this.  Use owner_before or std::owner_less instead.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -382,7 +502,7 @@ operator >= (const WeakPointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * Returns true if both pointers point to the same object.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -391,7 +511,7 @@ operator == (const PointerToBase<To> &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * Returns false if both pointers point to the same object.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -445,7 +565,8 @@ operator < (std::nullptr_t) const {
 }
 }
 
 
 /**
 /**
- *
+ * Defines an ordering between WeakPointerTo based on their raw pointer value.
+ * @deprecated Do not use this.  Use owner_before or std::owner_less instead.
  */
  */
 template<class T>
 template<class T>
 INLINE bool WeakPointerToBase<T>::
 INLINE bool WeakPointerToBase<T>::
@@ -464,6 +585,35 @@ operator < (const PointerToBase<To> &other) const {
 
 
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
+/**
+ * Defines an ordering that is guaranteed to remain consistent even after the
+ * weak pointers have expired.  This may result in two pointers with the same
+ * get_orig() value comparing unequal if one of them is a new object that was
+ * allocated at the same memory address as the older, expired pointer.
+ */
+template<class T>
+template<class Y>
+INLINE bool WeakPointerToBase<T>::
+owner_before(const WeakPointerToBase<Y> &other) const noexcept {
+  return _weak_ref < other._weak_ref;
+}
+
+/**
+ * Defines an ordering that is guaranteed to remain consistent even after this
+ * weak pointer has expired.  This may result in two pointers with the same
+ * get_orig() value comparing unequal if one of them is a new object that was
+ * allocated at the same memory address as the older, expired pointer.
+ */
+template<class T>
+template<class Y>
+INLINE bool WeakPointerToBase<T>::
+owner_before(const PointerToBase<Y> &other) const noexcept {
+  // Unfortunately, this may needlessly cause a control block to be allocated,
+  // but I do not see a more efficient solution.
+  return (other._void_ptr != nullptr) &&
+    (_void_ptr == nullptr || _weak_ref < ((const Y *)other._void_ptr)->get_weak_list());
+}
+
 /**
 /**
  * A convenient way to set the PointerTo object to NULL. (Assignment to a NULL
  * A convenient way to set the PointerTo object to NULL. (Assignment to a NULL
  * pointer also works, of course.)
  * pointer also works, of course.)
@@ -505,9 +655,17 @@ template<class T>
 INLINE void WeakPointerToBase<T>::
 INLINE void WeakPointerToBase<T>::
 output(std::ostream &out) const {
 output(std::ostream &out) const {
   out << _void_ptr;
   out << _void_ptr;
-  if (was_deleted()) {
-    out << ":deleted";
-  } else if (_void_ptr != nullptr) {
-    out << ":" << ((To *)_void_ptr)->get_ref_count();
+
+  WeakReferenceList *weak_ref = this->_weak_ref;
+  if (weak_ref != nullptr) {
+    weak_ref->_lock.lock();
+    if (!weak_ref->was_deleted()) {
+      out << ":" << ((To *)_void_ptr)->get_ref_count();
+    } else {
+      out << ":deleted";
+    }
+    weak_ref->_lock.unlock();
+  } else {
+    out << ":invalid";
   }
   }
 }
 }

+ 21 - 1
panda/src/express/weakPointerToBase.h

@@ -28,19 +28,31 @@ public:
   typedef T To;
   typedef T To;
 
 
 protected:
 protected:
-  INLINE WeakPointerToBase(To *ptr);
+  constexpr WeakPointerToBase() noexcept = default;
+  INLINE explicit WeakPointerToBase(To *ptr);
   INLINE WeakPointerToBase(const PointerToBase<T> &copy);
   INLINE WeakPointerToBase(const PointerToBase<T> &copy);
   INLINE WeakPointerToBase(const WeakPointerToBase<T> &copy);
   INLINE WeakPointerToBase(const WeakPointerToBase<T> &copy);
   INLINE WeakPointerToBase(WeakPointerToBase<T> &&from) noexcept;
   INLINE WeakPointerToBase(WeakPointerToBase<T> &&from) noexcept;
+  template<class Y>
+  INLINE WeakPointerToBase(const WeakPointerToBase<Y> &r);
+  template<class Y>
+  INLINE WeakPointerToBase(WeakPointerToBase<Y> &&r) noexcept;
+
   INLINE ~WeakPointerToBase();
   INLINE ~WeakPointerToBase();
 
 
   void reassign(To *ptr);
   void reassign(To *ptr);
   INLINE void reassign(const PointerToBase<To> &copy);
   INLINE void reassign(const PointerToBase<To> &copy);
   INLINE void reassign(const WeakPointerToBase<To> &copy);
   INLINE void reassign(const WeakPointerToBase<To> &copy);
   INLINE void reassign(WeakPointerToBase<To> &&from) noexcept;
   INLINE void reassign(WeakPointerToBase<To> &&from) noexcept;
+  template<class Y>
+  INLINE void reassign(const WeakPointerToBase<Y> &copy);
+  template<class Y>
+  INLINE void reassign(WeakPointerToBase<Y> &&from) noexcept;
 
 
   INLINE void update_type(To *ptr);
   INLINE void update_type(To *ptr);
 
 
+  INLINE void lock_into(PointerToBase<To> &locked) const;
+
   // No assignment or retrieval functions are declared in WeakPointerToBase,
   // No assignment or retrieval functions are declared in WeakPointerToBase,
   // because we will have to specialize on const vs.  non-const later.
   // because we will have to specialize on const vs.  non-const later.
 
 
@@ -83,6 +95,14 @@ public:
   INLINE bool operator < (const PointerToBase<To> &other) const;
   INLINE bool operator < (const PointerToBase<To> &other) const;
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
+  template<class Y>
+  INLINE bool owner_before(const WeakPointerToBase<Y> &other) const noexcept;
+  template<class Y>
+  INLINE bool owner_before(const PointerToBase<Y> &other) const noexcept;
+
+  // This is needed to be able to access the privates of other instantiations.
+  template<typename Y> friend class WeakPointerToBase;
+
 PUBLISHED:
 PUBLISHED:
   INLINE void clear();
   INLINE void clear();
   INLINE void refresh() const;
   INLINE void refresh() const;

+ 6 - 9
panda/src/express/weakPointerToVoid.I

@@ -11,13 +11,6 @@
  * @date 2004-09-27
  * @date 2004-09-27
  */
  */
 
 
-/**
- *
- */
-INLINE WeakPointerToVoid::
-WeakPointerToVoid() : _weak_ref(nullptr) {
-}
-
 /**
 /**
  * Sets a callback that will be made when the pointer is deleted.  Does
  * Sets a callback that will be made when the pointer is deleted.  Does
  * nothing if this is a null pointer.
  * nothing if this is a null pointer.
@@ -47,7 +40,11 @@ remove_callback(WeakPointerCallback *callback) const {
 
 
 /**
 /**
  * Returns true if the object we are pointing to has been deleted, false
  * Returns true if the object we are pointing to has been deleted, false
- * otherwise.
+ * otherwise.  If this returns true, it means that the pointer can not yet be
+ * reused, but it does not guarantee that it can be safely accessed.  See the
+ * lock() method for a safe way to access the underlying pointer.
+ *
+ * This will always return true for a null pointer, unlike is_valid_pointer().
  */
  */
 INLINE bool WeakPointerToVoid::
 INLINE bool WeakPointerToVoid::
 was_deleted() const {
 was_deleted() const {
@@ -56,7 +53,7 @@ was_deleted() const {
 
 
 /**
 /**
  * Returns true if the pointer is not null and the object has not been
  * Returns true if the pointer is not null and the object has not been
- * deleted.
+ * deleted.  See was_deleted() for caveats.
  */
  */
 INLINE bool WeakPointerToVoid::
 INLINE bool WeakPointerToVoid::
 is_valid_pointer() const {
 is_valid_pointer() const {

+ 2 - 2
panda/src/express/weakPointerToVoid.h

@@ -25,7 +25,7 @@
  */
  */
 class EXPCL_PANDA_EXPRESS WeakPointerToVoid : public PointerToVoid {
 class EXPCL_PANDA_EXPRESS WeakPointerToVoid : public PointerToVoid {
 protected:
 protected:
-  INLINE WeakPointerToVoid();
+  constexpr WeakPointerToVoid() noexcept = default;
 
 
 public:
 public:
   INLINE void add_callback(WeakPointerCallback *callback) const;
   INLINE void add_callback(WeakPointerCallback *callback) const;
@@ -36,7 +36,7 @@ PUBLISHED:
   INLINE bool is_valid_pointer() const;
   INLINE bool is_valid_pointer() const;
 
 
 protected:
 protected:
-  mutable WeakReferenceList *_weak_ref;
+  mutable WeakReferenceList *_weak_ref = nullptr;
 };
 };
 
 
 #include "weakPointerToVoid.I"
 #include "weakPointerToVoid.I"

+ 20 - 8
panda/src/glstuff/glGeomMunger_src.cxx

@@ -426,11 +426,17 @@ premunge_format_impl(const GeomVertexFormat *orig) {
 int CLP(GeomMunger)::
 int CLP(GeomMunger)::
 compare_to_impl(const GeomMunger *other) const {
 compare_to_impl(const GeomMunger *other) const {
   const CLP(GeomMunger) *om = (CLP(GeomMunger) *)other;
   const CLP(GeomMunger) *om = (CLP(GeomMunger) *)other;
-  if (_texture != om->_texture) {
-    return _texture < om->_texture ? -1 : 1;
+  if (_texture.owner_before(om->_texture)) {
+    return -1;
   }
   }
-  if (_tex_gen != om->_tex_gen) {
-    return _tex_gen < om->_tex_gen ? -1 : 1;
+  if (om->_texture.owner_before(_texture)) {
+    return 1;
+  }
+  if (_tex_gen.owner_before(om->_tex_gen)) {
+    return -1;
+  }
+  if (om->_tex_gen.owner_before(_tex_gen)) {
+    return 1;
   }
   }
   if (_flags != om->_flags) {
   if (_flags != om->_flags) {
     return _flags < om->_flags ? -1 : 1;
     return _flags < om->_flags ? -1 : 1;
@@ -447,11 +453,17 @@ compare_to_impl(const GeomMunger *other) const {
 int CLP(GeomMunger)::
 int CLP(GeomMunger)::
 geom_compare_to_impl(const GeomMunger *other) const {
 geom_compare_to_impl(const GeomMunger *other) const {
   const CLP(GeomMunger) *om = (CLP(GeomMunger) *)other;
   const CLP(GeomMunger) *om = (CLP(GeomMunger) *)other;
-  if (_texture != om->_texture) {
-    return _texture < om->_texture ? -1 : 1;
+  if (_texture.owner_before(om->_texture)) {
+    return -1;
+  }
+  if (om->_texture.owner_before(_texture)) {
+    return 1;
+  }
+  if (_tex_gen.owner_before(om->_tex_gen)) {
+    return -1;
   }
   }
-  if (_tex_gen != om->_tex_gen) {
-    return _tex_gen < om->_tex_gen ? -1 : 1;
+  if (om->_tex_gen.owner_before(_tex_gen)) {
+    return 1;
   }
   }
   if (_flags != om->_flags) {
   if (_flags != om->_flags) {
     return _flags < om->_flags ? -1 : 1;
     return _flags < om->_flags ? -1 : 1;

+ 11 - 7
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2286,8 +2286,10 @@ reset() {
   if (is_at_least_gl_version(4, 5) || has_extension("GL_ARB_direct_state_access")) {
   if (is_at_least_gl_version(4, 5) || has_extension("GL_ARB_direct_state_access")) {
     _glGenerateTextureMipmap = (PFNGLGENERATETEXTUREMIPMAPPROC)
     _glGenerateTextureMipmap = (PFNGLGENERATETEXTUREMIPMAPPROC)
       get_extension_func("glGenerateTextureMipmap");
       get_extension_func("glGenerateTextureMipmap");
+
+    _supports_dsa = true;
   } else {
   } else {
-    _glGenerateTextureMipmap = nullptr;
+    _supports_dsa = false;
   }
   }
 #endif
 #endif
 
 
@@ -2830,7 +2832,9 @@ reset() {
   // Check availability of anisotropic texture filtering.
   // Check availability of anisotropic texture filtering.
   _supports_anisotropy = false;
   _supports_anisotropy = false;
   _max_anisotropy = 1.0;
   _max_anisotropy = 1.0;
-  if (has_extension("GL_EXT_texture_filter_anisotropic")) {
+  if (is_at_least_gl_version(4, 6) ||
+      has_extension("GL_EXT_texture_filter_anisotropic") ||
+      has_extension("GL_ARB_texture_filter_anisotropic")) {
     GLfloat max_anisotropy;
     GLfloat max_anisotropy;
     glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy);
     glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy);
     _max_anisotropy = (PN_stdfloat)max_anisotropy;
     _max_anisotropy = (PN_stdfloat)max_anisotropy;
@@ -3237,7 +3241,7 @@ reset() {
   if (GLCAT.is_debug()) {
   if (GLCAT.is_debug()) {
     if (_supports_get_program_binary) {
     if (_supports_get_program_binary) {
       GLCAT.debug()
       GLCAT.debug()
-        << "Supported shader binary formats:\n";
+        << "Supported program binary formats:\n";
       GLCAT.debug() << " ";
       GLCAT.debug() << " ";
 
 
       pset<GLenum>::const_iterator it;
       pset<GLenum>::const_iterator it;
@@ -3249,7 +3253,7 @@ reset() {
       }
       }
       GLCAT.debug(false) << "\n";
       GLCAT.debug(false) << "\n";
     } else {
     } else {
-      GLCAT.debug() << "No shader binary formats supported.\n";
+      GLCAT.debug() << "No program binary formats supported.\n";
     }
     }
   }
   }
 #endif
 #endif
@@ -7633,7 +7637,6 @@ do_issue_material() {
   } else if (material->has_ambient()) {
   } else if (material->has_ambient()) {
     // The material specifies an ambient, but not a diffuse component.  The
     // The material specifies an ambient, but not a diffuse component.  The
     // diffuse component comes from the object's color.
     // diffuse component comes from the object's color.
-    call_glMaterialfv(face, GL_AMBIENT, material->get_ambient());
     if (has_material_force_color) {
     if (has_material_force_color) {
       glDisable(GL_COLOR_MATERIAL);
       glDisable(GL_COLOR_MATERIAL);
       call_glMaterialfv(face, GL_DIFFUSE, _material_force_color);
       call_glMaterialfv(face, GL_DIFFUSE, _material_force_color);
@@ -7643,11 +7646,11 @@ do_issue_material() {
 #endif  // OPENGLES
 #endif  // OPENGLES
       glEnable(GL_COLOR_MATERIAL);
       glEnable(GL_COLOR_MATERIAL);
     }
     }
+    call_glMaterialfv(face, GL_AMBIENT, material->get_ambient());
 
 
   } else if (material->has_diffuse()) {
   } else if (material->has_diffuse()) {
     // The material specifies a diffuse, but not an ambient component.  The
     // The material specifies a diffuse, but not an ambient component.  The
     // ambient component comes from the object's color.
     // ambient component comes from the object's color.
-    call_glMaterialfv(face, GL_DIFFUSE, material->get_diffuse());
     if (has_material_force_color) {
     if (has_material_force_color) {
       glDisable(GL_COLOR_MATERIAL);
       glDisable(GL_COLOR_MATERIAL);
       call_glMaterialfv(face, GL_AMBIENT, _material_force_color);
       call_glMaterialfv(face, GL_AMBIENT, _material_force_color);
@@ -7657,6 +7660,7 @@ do_issue_material() {
 #endif  // OPENGLES
 #endif  // OPENGLES
       glEnable(GL_COLOR_MATERIAL);
       glEnable(GL_COLOR_MATERIAL);
     }
     }
+    call_glMaterialfv(face, GL_DIFFUSE, material->get_diffuse());
 
 
   } else {
   } else {
     // The material specifies neither a diffuse nor an ambient component.
     // The material specifies neither a diffuse nor an ambient component.
@@ -13193,7 +13197,7 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 generate_mipmaps(CLP(TextureContext) *gtc) {
 generate_mipmaps(CLP(TextureContext) *gtc) {
 #ifndef OPENGLES
 #ifndef OPENGLES
-  if (_glGenerateTextureMipmap != nullptr) {
+  if (_supports_dsa) {
     // OpenGL 4.5 offers an easy way to do this without binding.
     // OpenGL 4.5 offers an easy way to do this without binding.
     _glGenerateTextureMipmap(gtc->_index);
     _glGenerateTextureMipmap(gtc->_index);
     return;
     return;

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

@@ -909,6 +909,7 @@ public:
   PFNGLBINDPROGRAMARBPROC _glBindProgram;
   PFNGLBINDPROGRAMARBPROC _glBindProgram;
 
 
 #ifndef OPENGLES
 #ifndef OPENGLES
+  bool _supports_dsa;
   PFNGLGENERATETEXTUREMIPMAPPROC _glGenerateTextureMipmap;
   PFNGLGENERATETEXTUREMIPMAPPROC _glGenerateTextureMipmap;
 #endif
 #endif
 
 

+ 0 - 12
panda/src/gobj/config_gobj.cxx

@@ -260,18 +260,6 @@ ConfigVariableBool cache_generated_shaders
  PRC_DESC("Set this true to cause all generated shaders to be cached in "
  PRC_DESC("Set this true to cause all generated shaders to be cached in "
           "memory.  This is useful to prevent unnecessary recompilation."));
           "memory.  This is useful to prevent unnecessary recompilation."));
 
 
-ConfigVariableBool enforce_attrib_lock
-("enforce-attrib-lock", true,
- PRC_DESC("When a MaterialAttrib, TextureAttrib, or LightAttrib is "
-          "constructed, the corresponding Material, Texture, or Light "
-          "is 'attrib locked.'  The attrib lock prevents qualitative "
-          "changes to the object.  This makes it possible to hardwire "
-          "information about material, light, and texture properties "
-          "into generated shaders.  This config variable can disable "
-          "the attrib lock.  Disabling the lock will break the shader "
-          "generator, but doing so may be necessary for backward "
-          "compatibility with old code."));
-
 ConfigVariableBool vertices_float64
 ConfigVariableBool vertices_float64
 ("vertices-float64", false,
 ("vertices-float64", false,
  PRC_DESC("When this is true, the default float format for vertices "
  PRC_DESC("When this is true, the default float format for vertices "

+ 0 - 1
panda/src/gobj/config_gobj.h

@@ -51,7 +51,6 @@ extern EXPCL_PANDA_GOBJ ConfigVariableBool connect_triangle_strips;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool preserve_triangle_strips;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool preserve_triangle_strips;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool dump_generated_shaders;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool dump_generated_shaders;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool cache_generated_shaders;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool cache_generated_shaders;
-extern EXPCL_PANDA_GOBJ ConfigVariableBool enforce_attrib_lock;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertices_float64;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertices_float64;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt vertex_column_alignment;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt vertex_column_alignment;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_animation_align_16;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_animation_align_16;

+ 49 - 12
panda/src/gobj/geom.cxx

@@ -195,6 +195,9 @@ offset_vertices(const GeomVertexData *data, int offset) {
   cdata->_data = (GeomVertexData *)data;
   cdata->_data = (GeomVertexData *)data;
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(data, current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -203,7 +206,7 @@ offset_vertices(const GeomVertexData *data, int offset) {
     prim->offset_vertices(offset);
     prim->offset_vertices(offset);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!prim->check_valid(data)) {
+    if (!prim->check_valid(&data_reader)) {
       gobj_cat.warning()
       gobj_cat.warning()
         << *prim << " is invalid for " << *data << ":\n";
         << *prim << " is invalid for " << *data << ":\n";
       prim->write(gobj_cat.warning(false), 4);
       prim->write(gobj_cat.warning(false), 4);
@@ -423,6 +426,9 @@ decompose_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -431,7 +437,7 @@ decompose_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -457,6 +463,9 @@ doubleside_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -465,7 +474,7 @@ doubleside_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -491,6 +500,9 @@ reverse_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -499,7 +511,7 @@ reverse_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -525,6 +537,9 @@ rotate_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -533,7 +548,7 @@ rotate_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -640,6 +655,9 @@ unify_in_place(int max_indices, bool preserve_order) {
     // primitives.)
     // primitives.)
     nassertv(false);
     nassertv(false);
   }
   }
+
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
 #endif
 #endif
 
 
   // Finally, iterate through the remaining primitives, and copy them to the
   // Finally, iterate through the remaining primitives, and copy them to the
@@ -649,7 +667,7 @@ unify_in_place(int max_indices, bool preserve_order) {
   for (npi = new_prims.begin(); npi != new_prims.end(); ++npi) {
   for (npi = new_prims.begin(); npi != new_prims.end(); ++npi) {
     GeomPrimitive *prim = (*npi).second;
     GeomPrimitive *prim = (*npi).second;
 
 
-    nassertv(prim->check_valid(cdata->_data.get_read_pointer(current_thread)));
+    nassertv(prim->check_valid(&data_reader));
 
 
     // Each new primitive, naturally, inherits the Geom's overall shade model.
     // Each new primitive, naturally, inherits the Geom's overall shade model.
     prim->set_shade_model(cdata->_shade_model);
     prim->set_shade_model(cdata->_shade_model);
@@ -748,6 +766,9 @@ make_lines_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -756,7 +777,7 @@ make_lines_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -782,6 +803,9 @@ make_points_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -790,7 +814,7 @@ make_points_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -816,6 +840,9 @@ make_patches_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -824,7 +851,7 @@ make_patches_in_place() {
     (*pi) = (GeomPrimitive *)new_prim.p();
     (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-    if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+    if (!new_prim->check_valid(&data_reader)) {
       all_is_valid = false;
       all_is_valid = false;
     }
     }
 #endif
 #endif
@@ -850,6 +877,9 @@ make_adjacency_in_place() {
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
+  GeomVertexDataPipelineReader data_reader(cdata->_data.get_read_pointer(current_thread), current_thread);
+  data_reader.check_array_readers();
+
   bool all_is_valid = true;
   bool all_is_valid = true;
 #endif
 #endif
   Primitives::iterator pi;
   Primitives::iterator pi;
@@ -859,7 +889,7 @@ make_adjacency_in_place() {
       (*pi) = (GeomPrimitive *)new_prim.p();
       (*pi) = (GeomPrimitive *)new_prim.p();
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
-      if (!new_prim->check_valid(cdata->_data.get_read_pointer(current_thread))) {
+      if (!new_prim->check_valid(&data_reader)) {
         all_is_valid = false;
         all_is_valid = false;
       }
       }
 #endif
 #endif
@@ -1465,13 +1495,20 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
  */
  */
 bool Geom::
 bool Geom::
 check_will_be_valid(const GeomVertexData *vertex_data) const {
 check_will_be_valid(const GeomVertexData *vertex_data) const {
-  CDReader cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+
+  CDReader cdata(_cycler, current_thread);
+
+  GeomVertexDataPipelineReader data_reader(vertex_data, current_thread);
+  data_reader.check_array_readers();
 
 
   Primitives::const_iterator pi;
   Primitives::const_iterator pi;
   for (pi = cdata->_primitives.begin();
   for (pi = cdata->_primitives.begin();
        pi != cdata->_primitives.end();
        pi != cdata->_primitives.end();
        ++pi) {
        ++pi) {
-    if (!(*pi).get_read_pointer()->check_valid(vertex_data)) {
+    GeomPrimitivePipelineReader reader((*pi).get_read_pointer(), current_thread);
+    reader.check_minmax();
+    if (!reader.check_valid(&data_reader)) {
       return false;
       return false;
     }
     }
   }
   }

+ 11 - 3
panda/src/gobj/geomPrimitive.I

@@ -223,11 +223,19 @@ get_modified() const {
 INLINE bool GeomPrimitive::
 INLINE bool GeomPrimitive::
 check_valid(const GeomVertexData *vertex_data) const {
 check_valid(const GeomVertexData *vertex_data) const {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
-  GeomPrimitivePipelineReader reader(this, current_thread);
-  reader.check_minmax();
   GeomVertexDataPipelineReader data_reader(vertex_data, current_thread);
   GeomVertexDataPipelineReader data_reader(vertex_data, current_thread);
   data_reader.check_array_readers();
   data_reader.check_array_readers();
-  return reader.check_valid(&data_reader);
+  return check_valid(&data_reader);
+}
+
+/**
+ *
+ */
+INLINE bool GeomPrimitive::
+check_valid(const GeomVertexDataPipelineReader *data_reader) const {
+  GeomPrimitivePipelineReader reader(this, data_reader->get_current_thread());
+  reader.check_minmax();
+  return reader.check_valid(data_reader);
 }
 }
 
 
 /**
 /**

+ 1 - 0
panda/src/gobj/geomPrimitive.h

@@ -146,6 +146,7 @@ PUBLISHED:
   bool request_resident(Thread *current_thread = Thread::get_current_thread()) const;
   bool request_resident(Thread *current_thread = Thread::get_current_thread()) const;
 
 
   INLINE bool check_valid(const GeomVertexData *vertex_data) const;
   INLINE bool check_valid(const GeomVertexData *vertex_data) const;
+  INLINE bool check_valid(const GeomVertexDataPipelineReader *data_reader) const;
 
 
   virtual void output(std::ostream &out) const;
   virtual void output(std::ostream &out) const;
   virtual void write(std::ostream &out, int indent_level) const;
   virtual void write(std::ostream &out, int indent_level) const;

+ 43 - 15
panda/src/gobj/material.I

@@ -32,8 +32,18 @@ Material(const std::string &name) : Namable(name) {
  *
  *
  */
  */
 INLINE Material::
 INLINE Material::
-Material(const Material &copy) : Namable(copy) {
-  operator = (copy);
+Material(const Material &copy) :
+  Namable(copy) ,
+  _base_color(copy._base_color),
+  _ambient(copy._ambient),
+  _diffuse(copy._diffuse),
+  _specular(copy._specular),
+  _emission(copy._emission),
+  _shininess(copy._shininess),
+  _roughness(copy._roughness),
+  _metallic(copy._metallic),
+  _refractive_index(copy._refractive_index),
+  _flags(copy._flags & ~(F_attrib_lock | F_used_by_auto_shader)) {
 }
 }
 
 
 /**
 /**
@@ -99,8 +109,8 @@ get_ambient() const {
  */
  */
 INLINE void Material::
 INLINE void Material::
 clear_ambient() {
 clear_ambient() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (has_ambient() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _flags &= ~F_ambient;
   _flags &= ~F_ambient;
   _ambient = _base_color;
   _ambient = _base_color;
@@ -129,8 +139,8 @@ get_diffuse() const {
  */
  */
 INLINE void Material::
 INLINE void Material::
 clear_diffuse() {
 clear_diffuse() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (has_diffuse() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _flags &= ~F_diffuse;
   _flags &= ~F_diffuse;
   _diffuse = _base_color * (1 - _metallic);
   _diffuse = _base_color * (1 - _metallic);
@@ -177,8 +187,8 @@ get_emission() const {
  */
  */
 INLINE void Material::
 INLINE void Material::
 clear_emission() {
 clear_emission() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (has_emission() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _flags &= ~F_emission;
   _flags &= ~F_emission;
   _emission.set(0.0f, 0.0f, 0.0f, 0.0f);
   _emission.set(0.0f, 0.0f, 0.0f, 0.0f);
@@ -253,8 +263,8 @@ get_local() const {
  */
  */
 INLINE void Material::
 INLINE void Material::
 set_local(bool local) {
 set_local(bool local) {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (is_used_by_auto_shader() && get_local() != local) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   if (local) {
   if (local) {
     _flags |= F_local;
     _flags |= F_local;
@@ -278,8 +288,8 @@ get_twoside() const {
  */
  */
 INLINE void Material::
 INLINE void Material::
 set_twoside(bool twoside) {
 set_twoside(bool twoside) {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (is_used_by_auto_shader() && get_twoside() != twoside) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   if (twoside) {
   if (twoside) {
     _flags |= F_twoside;
     _flags |= F_twoside;
@@ -313,7 +323,7 @@ operator < (const Material &other) const {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated This no longer has any meaning in 1.10.
  */
  */
 INLINE bool Material::
 INLINE bool Material::
 is_attrib_locked() const {
 is_attrib_locked() const {
@@ -321,17 +331,35 @@ is_attrib_locked() const {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated This no longer has any meaning in 1.10.
  */
  */
 INLINE void Material::
 INLINE void Material::
 set_attrib_lock() {
 set_attrib_lock() {
   _flags |= F_attrib_lock;
   _flags |= F_attrib_lock;
 }
 }
 
 
+/**
+ * Internal.  Returns true if a shader has been generated that uses this.
+ */
+INLINE bool Material::
+is_used_by_auto_shader() const {
+  return (_flags & F_attrib_lock) != 0;
+}
+
+/**
+ * Called by the shader generator to indicate that a shader has been generated
+ * that uses this material.
+ */
+INLINE void Material::
+mark_used_by_auto_shader() {
+  _flags |= F_used_by_auto_shader;
+}
+
 /**
 /**
  *
  *
  */
  */
 INLINE int Material::
 INLINE int Material::
 get_flags() const {
 get_flags() const {
-  return _flags;
+  // F_used_by_auto_shader is an internal flag, ignore it.
+  return _flags & ~F_used_by_auto_shader;
 }
 }

+ 25 - 34
panda/src/gobj/material.cxx

@@ -28,6 +28,11 @@ PT(Material) Material::_default;
 void Material::
 void Material::
 operator = (const Material &copy) {
 operator = (const Material &copy) {
   Namable::operator = (copy);
   Namable::operator = (copy);
+
+  if (is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
+
   _base_color = copy._base_color;
   _base_color = copy._base_color;
   _ambient = copy._ambient;
   _ambient = copy._ambient;
   _diffuse = copy._diffuse;
   _diffuse = copy._diffuse;
@@ -37,7 +42,7 @@ operator = (const Material &copy) {
   _roughness = copy._roughness;
   _roughness = copy._roughness;
   _metallic = copy._metallic;
   _metallic = copy._metallic;
   _refractive_index = copy._refractive_index;
   _refractive_index = copy._refractive_index;
-  _flags = copy._flags & (~F_attrib_lock);
+  _flags = (copy._flags & ~(F_attrib_lock | F_used_by_auto_shader)) | (_flags & (F_attrib_lock | F_used_by_auto_shader));
 }
 }
 
 
 /**
 /**
@@ -53,10 +58,8 @@ operator = (const Material &copy) {
  */
  */
 void Material::
 void Material::
 set_base_color(const LColor &color) {
 set_base_color(const LColor &color) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_base_color) == 0) {
-      nassertv(!is_attrib_locked());
-    }
+  if (!has_base_color() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _base_color = color;
   _base_color = color;
   _flags |= F_base_color | F_metallic;
   _flags |= F_base_color | F_metallic;
@@ -81,8 +84,8 @@ set_base_color(const LColor &color) {
  */
  */
 void Material::
 void Material::
 clear_base_color() {
 clear_base_color() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (has_base_color() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _flags &= ~F_base_color;
   _flags &= ~F_base_color;
   _base_color.set(0.0f, 0.0f, 0.0f, 0.0f);
   _base_color.set(0.0f, 0.0f, 0.0f, 0.0f);
@@ -116,10 +119,8 @@ clear_base_color() {
  */
  */
 void Material::
 void Material::
 set_ambient(const LColor &color) {
 set_ambient(const LColor &color) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_ambient)==0) {
-      nassertv(!is_attrib_locked());
-    }
+  if (!has_ambient() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _ambient = color;
   _ambient = color;
   _flags |= F_ambient;
   _flags |= F_ambient;
@@ -137,10 +138,8 @@ set_ambient(const LColor &color) {
  */
  */
 void Material::
 void Material::
 set_diffuse(const LColor &color) {
 set_diffuse(const LColor &color) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_diffuse)==0) {
-      nassertv(!is_attrib_locked());
-    }
+  if (!has_diffuse() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _diffuse = color;
   _diffuse = color;
   _flags |= F_diffuse;
   _flags |= F_diffuse;
@@ -160,10 +159,8 @@ set_diffuse(const LColor &color) {
  */
  */
 void Material::
 void Material::
 set_specular(const LColor &color) {
 set_specular(const LColor &color) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_specular)==0) {
-      nassertv(!is_attrib_locked());
-    }
+  if (!has_specular() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _specular = color;
   _specular = color;
   _flags |= F_specular;
   _flags |= F_specular;
@@ -174,8 +171,8 @@ set_specular(const LColor &color) {
  */
  */
 void Material::
 void Material::
 clear_specular() {
 clear_specular() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
+  if (has_specular() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _flags &= ~F_specular;
   _flags &= ~F_specular;
 
 
@@ -201,10 +198,8 @@ clear_specular() {
  */
  */
 void Material::
 void Material::
 set_emission(const LColor &color) {
 set_emission(const LColor &color) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_emission)==0) {
-      nassertv(!is_attrib_locked());
-    }
+  if (!has_emission() && is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
   }
   }
   _emission = color;
   _emission = color;
   _flags |= F_emission;
   _flags |= F_emission;
@@ -275,11 +270,6 @@ set_roughness(PN_stdfloat roughness) {
  */
  */
 void Material::
 void Material::
 set_metallic(PN_stdfloat metallic) {
 set_metallic(PN_stdfloat metallic) {
-  if (enforce_attrib_lock) {
-    if ((_flags & F_metallic) == 0) {
-      nassertv(!is_attrib_locked());
-    }
-  }
   _metallic = metallic;
   _metallic = metallic;
   _flags |= F_metallic;
   _flags |= F_metallic;
 
 
@@ -305,9 +295,6 @@ set_metallic(PN_stdfloat metallic) {
  */
  */
 void Material::
 void Material::
 clear_metallic() {
 clear_metallic() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
-  }
   _flags &= ~F_metallic;
   _flags &= ~F_metallic;
   _metallic = 0;
   _metallic = 0;
 
 
@@ -482,7 +469,7 @@ write_datagram(BamWriter *manager, Datagram &me) {
   me.add_string(get_name());
   me.add_string(get_name());
 
 
   if (manager->get_file_minor_ver() >= 39) {
   if (manager->get_file_minor_ver() >= 39) {
-    me.add_int32(_flags);
+    me.add_int32(_flags & ~F_used_by_auto_shader);
 
 
     if (_flags & F_metallic) {
     if (_flags & F_metallic) {
       // Metalness workflow.
       // Metalness workflow.
@@ -570,4 +557,8 @@ fillin(DatagramIterator &scan, BamReader *manager) {
       set_roughness(_shininess);
       set_roughness(_shininess);
     }
     }
   }
   }
+
+  if (is_used_by_auto_shader()) {
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
 }
 }

+ 6 - 0
panda/src/gobj/material.h

@@ -21,6 +21,7 @@
 #include "luse.h"
 #include "luse.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
 #include "config_gobj.h"
 #include "config_gobj.h"
+#include "graphicsStateGuardianBase.h"
 
 
 class FactoryParams;
 class FactoryParams;
 
 
@@ -127,7 +128,11 @@ PUBLISHED:
   MAKE_PROPERTY(local, get_local, set_local);
   MAKE_PROPERTY(local, get_local, set_local);
   MAKE_PROPERTY(twoside, get_twoside, set_twoside);
   MAKE_PROPERTY(twoside, get_twoside, set_twoside);
 
 
+protected:
+  INLINE bool is_used_by_auto_shader() const;
+
 public:
 public:
+  INLINE void mark_used_by_auto_shader();
   INLINE int get_flags() const;
   INLINE int get_flags() const;
 
 
   enum Flags {
   enum Flags {
@@ -142,6 +147,7 @@ public:
     F_metallic    = 0x100,
     F_metallic    = 0x100,
     F_base_color  = 0x200,
     F_base_color  = 0x200,
     F_refractive_index = 0x400,
     F_refractive_index = 0x400,
+    F_used_by_auto_shader = 0x800,
   };
   };
 
 
 private:
 private:

+ 0 - 25
panda/src/gobj/test_gobj.cxx

@@ -1,25 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file test_gobj.cxx
- * @author shochet
- * @date 2000-02-02
- */
-
-#include "geom.h"
-#include "perspectiveProjection.h"
-
-int main() {
-  nout << "running test_gobj" << std::endl;
-  PT(GeomTri) triangle = new GeomTri;
-  Frustumf frust;
-  PT(PerspectiveProjection) proj = new PerspectiveProjection(frust);
-  LMatrix4f mat = proj->get_projection_mat();
-  nout << "default proj matrix: " << mat;
-  return 0;
-}

+ 20 - 0
panda/src/gobj/texturePool.I

@@ -275,3 +275,23 @@ PT(Texture) TexturePool::
 make_texture(const std::string &extension) {
 make_texture(const std::string &extension) {
   return get_global_ptr()->ns_make_texture(extension);
   return get_global_ptr()->ns_make_texture(extension);
 }
 }
+
+/**
+ * Defines relative ordering between LookupKey instances.
+ */
+INLINE bool TexturePool::LookupKey::
+operator < (const LookupKey &other) const {
+  if (_fullpath != other._fullpath) {
+    return _fullpath < other._fullpath;
+  }
+  if (_alpha_fullpath != other._alpha_fullpath) {
+    return _alpha_fullpath < other._alpha_fullpath;
+  }
+  if (_primary_file_num_channels != other._primary_file_num_channels) {
+    return _primary_file_num_channels < other._primary_file_num_channels;
+  }
+  if (_alpha_file_channel != other._alpha_file_channel) {
+    return _alpha_file_channel < other._alpha_file_channel;
+  }
+  return _texture_type < other._texture_type;
+}

+ 129 - 110
panda/src/gobj/texturePool.cxx

@@ -177,16 +177,23 @@ bool TexturePool::
 ns_has_texture(const Filename &orig_filename) {
 ns_has_texture(const Filename &orig_filename) {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
 
 
-  Filename filename;
-  resolve_filename(filename, orig_filename, false, LoaderOptions());
+  LookupKey key;
+  resolve_filename(key._fullpath, orig_filename, false, LoaderOptions());
 
 
   Textures::const_iterator ti;
   Textures::const_iterator ti;
-  ti = _textures.find(filename);
+  ti = _textures.find(key);
   if (ti != _textures.end()) {
   if (ti != _textures.end()) {
     // This texture was previously loaded.
     // This texture was previously loaded.
     return true;
     return true;
   }
   }
 
 
+  // It might still have been loaded with non-standard settings.
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    if (ti->first._fullpath == key._fullpath) {
+      return true;
+    }
+  }
+
   return false;
   return false;
 }
 }
 
 
@@ -196,13 +203,14 @@ ns_has_texture(const Filename &orig_filename) {
 Texture *TexturePool::
 Texture *TexturePool::
 ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
 ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
                 bool read_mipmaps, const LoaderOptions &options) {
                 bool read_mipmaps, const LoaderOptions &options) {
-  Filename filename;
-
+  LookupKey key;
+  key._primary_file_num_channels = primary_file_num_channels;
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
-    resolve_filename(filename, orig_filename, read_mipmaps, options);
+    resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
+
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
       Texture *tex = (*ti).second;
       Texture *tex = (*ti).second;
@@ -222,54 +230,54 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
 
 
   BamCache *cache = BamCache::get_global_ptr();
   BamCache *cache = BamCache::get_global_ptr();
   bool compressed_cache_record = false;
   bool compressed_cache_record = false;
-  try_load_cache(tex, cache, filename, record, compressed_cache_record,
+  try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
                  options);
                  options);
 
 
   if (tex == nullptr) {
   if (tex == nullptr) {
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // needs to be loaded from its source image(s).
     // needs to be loaded from its source image(s).
     gobj_cat.info()
     gobj_cat.info()
-      << "Loading texture " << filename << "\n";
+      << "Loading texture " << key._fullpath << "\n";
 
 
-    string ext = downcase(filename.get_extension());
+    string ext = downcase(key._fullpath.get_extension());
     if (ext == "txo" || ext == "bam") {
     if (ext == "txo" || ext == "bam") {
       // Assume this is a txo file, which might conceivably contain a movie
       // Assume this is a txo file, which might conceivably contain a movie
       // file or some other subclass of Texture.  In that case, use
       // file or some other subclass of Texture.  In that case, use
       // make_from_txo() to load it instead of read().
       // make_from_txo() to load it instead of read().
       VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
       VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
 
 
-      filename.set_binary();
-      PT(VirtualFile) file = vfs->get_file(filename);
+      key._fullpath.set_binary();
+      PT(VirtualFile) file = vfs->get_file(key._fullpath);
       if (file == nullptr) {
       if (file == nullptr) {
         // No such file.
         // No such file.
         gobj_cat.error()
         gobj_cat.error()
-          << "Could not find " << filename << "\n";
+          << "Could not find " << key._fullpath << "\n";
         return nullptr;
         return nullptr;
       }
       }
 
 
       if (gobj_cat.is_debug()) {
       if (gobj_cat.is_debug()) {
         gobj_cat.debug()
         gobj_cat.debug()
-          << "Reading texture object " << filename << "\n";
+          << "Reading texture object " << key._fullpath << "\n";
       }
       }
 
 
       istream *in = file->open_read_file(true);
       istream *in = file->open_read_file(true);
-      tex = Texture::make_from_txo(*in, filename);
+      tex = Texture::make_from_txo(*in, key._fullpath);
       vfs->close_read_file(in);
       vfs->close_read_file(in);
 
 
       if (tex == nullptr) {
       if (tex == nullptr) {
         return nullptr;
         return nullptr;
       }
       }
-      tex->set_fullpath(filename);
+      tex->set_fullpath(key._fullpath);
       tex->clear_alpha_fullpath();
       tex->clear_alpha_fullpath();
       tex->set_keep_ram_image(false);
       tex->set_keep_ram_image(false);
 
 
     } else {
     } else {
       // Read it the conventional way.
       // Read it the conventional way.
       tex = ns_make_texture(ext);
       tex = ns_make_texture(ext);
-      if (!tex->read(filename, Filename(), primary_file_num_channels, 0,
+      if (!tex->read(key._fullpath, Filename(), primary_file_num_channels, 0,
                      0, 0, false, read_mipmaps, record, options)) {
                      0, 0, false, read_mipmaps, record, options)) {
         // This texture was not found or could not be read.
         // This texture was not found or could not be read.
-        report_texture_unreadable(filename);
+        report_texture_unreadable(key._fullpath);
         return nullptr;
         return nullptr;
       }
       }
     }
     }
@@ -304,8 +312,8 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
   // Set the original filename, before we searched along the path.
   // Set the original filename, before we searched along the path.
   nassertr(tex != nullptr, nullptr);
   nassertr(tex != nullptr, nullptr);
   tex->set_filename(orig_filename);
   tex->set_filename(orig_filename);
-  tex->set_fullpath(filename);
-  tex->_texture_pool_key = filename;
+  tex->set_fullpath(key._fullpath);
+  tex->_texture_pool_key = key._fullpath;
 
 
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
@@ -313,7 +321,7 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
     // Now look again--someone may have just loaded this texture in another
     // Now look again--someone may have just loaded this texture in another
     // thread.
     // thread.
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
       Texture *tex = (*ti).second;
       Texture *tex = (*ti).second;
@@ -321,7 +329,7 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
       return tex;
       return tex;
     }
     }
 
 
-    _textures[filename] = tex;
+    _textures[std::move(key)] = tex;
   }
   }
 
 
   if (store_record && tex->is_cacheable()) {
   if (store_record && tex->is_cacheable()) {
@@ -357,16 +365,16 @@ ns_load_texture(const Filename &orig_filename,
                            read_mipmaps, options);
                            read_mipmaps, options);
   }
   }
 
 
-  Filename filename;
-  Filename alpha_filename;
-
+  LookupKey key;
+  key._primary_file_num_channels = primary_file_num_channels;
+  key._alpha_file_channel = alpha_file_channel;
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
-    resolve_filename(filename, orig_filename, read_mipmaps, options);
-    resolve_filename(alpha_filename, orig_alpha_filename, read_mipmaps, options);
+    resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
+    resolve_filename(key._alpha_fullpath, orig_alpha_filename, read_mipmaps, options);
 
 
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
       Texture *tex = (*ti).second;
       Texture *tex = (*ti).second;
@@ -380,26 +388,26 @@ ns_load_texture(const Filename &orig_filename,
   bool store_record = false;
   bool store_record = false;
 
 
   // Can one of our texture filters supply the texture?
   // Can one of our texture filters supply the texture?
-  tex = pre_load(orig_filename, alpha_filename, primary_file_num_channels,
+  tex = pre_load(orig_filename, orig_alpha_filename, primary_file_num_channels,
                  alpha_file_channel, read_mipmaps, options);
                  alpha_file_channel, read_mipmaps, options);
 
 
   BamCache *cache = BamCache::get_global_ptr();
   BamCache *cache = BamCache::get_global_ptr();
   bool compressed_cache_record = false;
   bool compressed_cache_record = false;
-  try_load_cache(tex, cache, filename, record, compressed_cache_record,
+  try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
                  options);
                  options);
 
 
   if (tex == nullptr) {
   if (tex == nullptr) {
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // needs to be loaded from its source image(s).
     // needs to be loaded from its source image(s).
     gobj_cat.info()
     gobj_cat.info()
-      << "Loading texture " << filename << " and alpha component "
-      << alpha_filename << std::endl;
-    tex = ns_make_texture(filename.get_extension());
-    if (!tex->read(filename, alpha_filename, primary_file_num_channels,
+      << "Loading texture " << key._fullpath << " and alpha component "
+      << key._alpha_fullpath << std::endl;
+    tex = ns_make_texture(key._fullpath.get_extension());
+    if (!tex->read(key._fullpath, key._alpha_fullpath, primary_file_num_channels,
                    alpha_file_channel, 0, 0, false, read_mipmaps, nullptr,
                    alpha_file_channel, 0, 0, false, read_mipmaps, nullptr,
                    options)) {
                    options)) {
       // This texture was not found or could not be read.
       // This texture was not found or could not be read.
-      report_texture_unreadable(filename);
+      report_texture_unreadable(key._fullpath);
       return nullptr;
       return nullptr;
     }
     }
 
 
@@ -433,17 +441,17 @@ ns_load_texture(const Filename &orig_filename,
   // Set the original filenames, before we searched along the path.
   // Set the original filenames, before we searched along the path.
   nassertr(tex != nullptr, nullptr);
   nassertr(tex != nullptr, nullptr);
   tex->set_filename(orig_filename);
   tex->set_filename(orig_filename);
-  tex->set_fullpath(filename);
+  tex->set_fullpath(key._fullpath);
   tex->set_alpha_filename(orig_alpha_filename);
   tex->set_alpha_filename(orig_alpha_filename);
-  tex->set_alpha_fullpath(alpha_filename);
-  tex->_texture_pool_key = filename;
+  tex->set_alpha_fullpath(key._alpha_fullpath);
+  tex->_texture_pool_key = key._fullpath;
 
 
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
 
 
     // Now look again.
     // Now look again.
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
       Texture *tex = (*ti).second;
       Texture *tex = (*ti).second;
@@ -451,7 +459,7 @@ ns_load_texture(const Filename &orig_filename,
       return tex;
       return tex;
     }
     }
 
 
-    _textures[filename] = tex;
+    _textures[std::move(key)] = tex;
   }
   }
 
 
   if (store_record && tex->is_cacheable()) {
   if (store_record && tex->is_cacheable()) {
@@ -482,18 +490,19 @@ ns_load_3d_texture(const Filename &filename_pattern,
   Filename orig_filename(filename_pattern);
   Filename orig_filename(filename_pattern);
   orig_filename.set_pattern(true);
   orig_filename.set_pattern(true);
 
 
-  Filename filename;
+  LookupKey key;
+  key._texture_type = Texture::TT_3d_texture;
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
-    resolve_filename(filename, orig_filename, read_mipmaps, options);
+    resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
 
 
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
-      if ((*ti).second->get_texture_type() == Texture::TT_3d_texture) {
-        // This texture was previously loaded, as a 3d texture
-        return (*ti).second;
-      }
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
   }
   }
 
 
@@ -503,7 +512,7 @@ ns_load_3d_texture(const Filename &filename_pattern,
 
 
   BamCache *cache = BamCache::get_global_ptr();
   BamCache *cache = BamCache::get_global_ptr();
   bool compressed_cache_record = false;
   bool compressed_cache_record = false;
-  try_load_cache(tex, cache, filename, record, compressed_cache_record,
+  try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
                  options);
                  options);
 
 
   if (tex == nullptr ||
   if (tex == nullptr ||
@@ -511,12 +520,12 @@ ns_load_3d_texture(const Filename &filename_pattern,
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // needs to be loaded from its source image(s).
     // needs to be loaded from its source image(s).
     gobj_cat.info()
     gobj_cat.info()
-      << "Loading 3-d texture " << filename << "\n";
-    tex = ns_make_texture(filename.get_extension());
+      << "Loading 3-d texture " << key._fullpath << "\n";
+    tex = ns_make_texture(key._fullpath.get_extension());
     tex->setup_3d_texture();
     tex->setup_3d_texture();
-    if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
+    if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
       // This texture was not found or could not be read.
       // This texture was not found or could not be read.
-      report_texture_unreadable(filename);
+      report_texture_unreadable(key._fullpath);
       return nullptr;
       return nullptr;
     }
     }
     store_record = (record != nullptr);
     store_record = (record != nullptr);
@@ -545,23 +554,23 @@ ns_load_3d_texture(const Filename &filename_pattern,
   // Set the original filename, before we searched along the path.
   // Set the original filename, before we searched along the path.
   nassertr(tex != nullptr, nullptr);
   nassertr(tex != nullptr, nullptr);
   tex->set_filename(filename_pattern);
   tex->set_filename(filename_pattern);
-  tex->set_fullpath(filename);
-  tex->_texture_pool_key = filename;
+  tex->set_fullpath(key._fullpath);
+  tex->_texture_pool_key = key._fullpath;
 
 
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
 
 
     // Now look again.
     // Now look again.
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
-      if ((*ti).second->get_texture_type() == Texture::TT_3d_texture) {
-        // This texture was previously loaded, as a 3d texture
-        return (*ti).second;
-      }
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
 
 
-    _textures[filename] = tex;
+    _textures[std::move(key)] = tex;
   }
   }
 
 
   if (store_record && tex->is_cacheable()) {
   if (store_record && tex->is_cacheable()) {
@@ -583,21 +592,19 @@ ns_load_2d_texture_array(const Filename &filename_pattern,
   Filename orig_filename(filename_pattern);
   Filename orig_filename(filename_pattern);
   orig_filename.set_pattern(true);
   orig_filename.set_pattern(true);
 
 
-  Filename filename;
-  Filename unique_filename; //differentiate 3d-textures from 2d-texture arrays
+  LookupKey key;
+  key._texture_type = Texture::TT_2d_texture_array;
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
-    resolve_filename(filename, orig_filename, read_mipmaps, options);
-    // Differentiate from preloaded 3d textures
-    unique_filename = filename + ".2DARRAY";
+    resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
 
 
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(unique_filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
-      if ((*ti).second->get_texture_type() == Texture::TT_2d_texture_array) {
-        // This texture was previously loaded, as a 2d texture array
-        return (*ti).second;
-      }
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
   }
   }
 
 
@@ -607,7 +614,7 @@ ns_load_2d_texture_array(const Filename &filename_pattern,
 
 
   BamCache *cache = BamCache::get_global_ptr();
   BamCache *cache = BamCache::get_global_ptr();
   bool compressed_cache_record = false;
   bool compressed_cache_record = false;
-  try_load_cache(tex, cache, filename, record, compressed_cache_record,
+  try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
                  options);
                  options);
 
 
   if (tex == nullptr ||
   if (tex == nullptr ||
@@ -615,12 +622,12 @@ ns_load_2d_texture_array(const Filename &filename_pattern,
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // needs to be loaded from its source image(s).
     // needs to be loaded from its source image(s).
     gobj_cat.info()
     gobj_cat.info()
-      << "Loading 2-d texture array " << filename << "\n";
-    tex = ns_make_texture(filename.get_extension());
+      << "Loading 2-d texture array " << key._fullpath << "\n";
+    tex = ns_make_texture(key._fullpath.get_extension());
     tex->setup_2d_texture_array();
     tex->setup_2d_texture_array();
-    if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
+    if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
       // This texture was not found or could not be read.
       // This texture was not found or could not be read.
-      report_texture_unreadable(filename);
+      report_texture_unreadable(key._fullpath);
       return nullptr;
       return nullptr;
     }
     }
     store_record = (record != nullptr);
     store_record = (record != nullptr);
@@ -649,23 +656,23 @@ ns_load_2d_texture_array(const Filename &filename_pattern,
   // Set the original filename, before we searched along the path.
   // Set the original filename, before we searched along the path.
   nassertr(tex != nullptr, nullptr);
   nassertr(tex != nullptr, nullptr);
   tex->set_filename(filename_pattern);
   tex->set_filename(filename_pattern);
-  tex->set_fullpath(filename);
-  tex->_texture_pool_key = unique_filename;
+  tex->set_fullpath(key._fullpath);
+  tex->_texture_pool_key = key._fullpath;
 
 
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
 
 
     // Now look again.
     // Now look again.
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(unique_filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
-      if ((*ti).second->get_texture_type() == Texture::TT_2d_texture_array) {
-        // This texture was previously loaded, as a 2d texture array
-        return (*ti).second;
-      }
+      // This texture was previously loaded.
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
 
 
-    _textures[unique_filename] = tex;
+    _textures[std::move(key)] = tex;
   }
   }
 
 
   if (store_record && tex->is_cacheable()) {
   if (store_record && tex->is_cacheable()) {
@@ -687,16 +694,19 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
   Filename orig_filename(filename_pattern);
   Filename orig_filename(filename_pattern);
   orig_filename.set_pattern(true);
   orig_filename.set_pattern(true);
 
 
-  Filename filename;
+  LookupKey key;
+  key._texture_type = Texture::TT_cube_map;
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
-    resolve_filename(filename, orig_filename, read_mipmaps, options);
+    resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
 
 
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
-      return (*ti).second;
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
   }
   }
 
 
@@ -706,7 +716,7 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
 
 
   BamCache *cache = BamCache::get_global_ptr();
   BamCache *cache = BamCache::get_global_ptr();
   bool compressed_cache_record = false;
   bool compressed_cache_record = false;
-  try_load_cache(tex, cache, filename, record, compressed_cache_record,
+  try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
                  options);
                  options);
 
 
   if (tex == nullptr ||
   if (tex == nullptr ||
@@ -714,12 +724,12 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // The texture was neither in the pool, nor found in the on-disk cache; it
     // needs to be loaded from its source image(s).
     // needs to be loaded from its source image(s).
     gobj_cat.info()
     gobj_cat.info()
-      << "Loading cube map texture " << filename << "\n";
-    tex = ns_make_texture(filename.get_extension());
+      << "Loading cube map texture " << key._fullpath << "\n";
+    tex = ns_make_texture(key._fullpath.get_extension());
     tex->setup_cube_map();
     tex->setup_cube_map();
-    if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
+    if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
       // This texture was not found or could not be read.
       // This texture was not found or could not be read.
-      report_texture_unreadable(filename);
+      report_texture_unreadable(key._fullpath);
       return nullptr;
       return nullptr;
     }
     }
     store_record = (record != nullptr);
     store_record = (record != nullptr);
@@ -748,21 +758,23 @@ ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
   // Set the original filename, before we searched along the path.
   // Set the original filename, before we searched along the path.
   nassertr(tex != nullptr, nullptr);
   nassertr(tex != nullptr, nullptr);
   tex->set_filename(filename_pattern);
   tex->set_filename(filename_pattern);
-  tex->set_fullpath(filename);
-  tex->_texture_pool_key = filename;
+  tex->set_fullpath(key._fullpath);
+  tex->_texture_pool_key = key._fullpath;
 
 
   {
   {
     MutexHolder holder(_lock);
     MutexHolder holder(_lock);
 
 
     // Now look again.
     // Now look again.
     Textures::const_iterator ti;
     Textures::const_iterator ti;
-    ti = _textures.find(filename);
+    ti = _textures.find(key);
     if (ti != _textures.end()) {
     if (ti != _textures.end()) {
       // This texture was previously loaded.
       // This texture was previously loaded.
-      return (*ti).second;
+      Texture *tex = (*ti).second;
+      nassertr(!tex->get_fullpath().empty(), tex);
+      return tex;
     }
     }
 
 
-    _textures[filename] = tex;
+    _textures[std::move(key)] = tex;
   }
   }
 
 
   if (store_record && tex->is_cacheable()) {
   if (store_record && tex->is_cacheable()) {
@@ -819,15 +831,22 @@ ns_add_texture(Texture *tex) {
   if (!tex->_texture_pool_key.empty()) {
   if (!tex->_texture_pool_key.empty()) {
     ns_release_texture(tex);
     ns_release_texture(tex);
   }
   }
-  string filename = tex->get_fullpath();
-  if (filename.empty()) {
+
+  Texture::CDReader tex_cdata(tex->_cycler);
+  if (tex_cdata->_fullpath.empty()) {
     gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
     gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
+    return;
   }
   }
 
 
+  LookupKey key;
+  key._fullpath = tex_cdata->_fullpath;
+  key._alpha_fullpath = tex_cdata->_alpha_fullpath;
+  key._alpha_file_channel = tex_cdata->_alpha_file_channel;
+  key._texture_type = tex_cdata->_texture_type;
+
   // We blow away whatever texture was there previously, if any.
   // We blow away whatever texture was there previously, if any.
-  tex->_texture_pool_key = filename;
-  _textures[filename] = tex;
-  nassertv(!tex->get_fullpath().empty());
+  tex->_texture_pool_key = key._fullpath;
+  _textures[key] = tex;
 }
 }
 
 
 /**
 /**
@@ -837,13 +856,13 @@ void TexturePool::
 ns_release_texture(Texture *tex) {
 ns_release_texture(Texture *tex) {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
 
 
-  if (!tex->_texture_pool_key.empty()) {
-    Textures::iterator ti;
-    ti = _textures.find(tex->_texture_pool_key);
-    if (ti != _textures.end() && (*ti).second == tex) {
+  Textures::iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    if (tex == (*ti).second) {
       _textures.erase(ti);
       _textures.erase(ti);
+      tex->_texture_pool_key = string();
+      break;
     }
     }
-    tex->_texture_pool_key = string();
   }
   }
 
 
   // Blow away the cache of resolved relative filenames.
   // Blow away the cache of resolved relative filenames.
@@ -886,7 +905,7 @@ ns_garbage_collect() {
     if (tex->get_ref_count() == 1) {
     if (tex->get_ref_count() == 1) {
       if (gobj_cat.is_debug()) {
       if (gobj_cat.is_debug()) {
         gobj_cat.debug()
         gobj_cat.debug()
-          << "Releasing " << (*ti).first << "\n";
+          << "Releasing " << (*ti).first._fullpath << "\n";
       }
       }
       ++num_released;
       ++num_released;
       tex->_texture_pool_key = string();
       tex->_texture_pool_key = string();
@@ -927,14 +946,14 @@ ns_list_contents(ostream &out) const {
   total_ram_size = 0;
   total_ram_size = 0;
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
     Texture *tex = (*ti).second;
     Texture *tex = (*ti).second;
-    out << (*ti).first << "\n";
+    out << (*ti).first._fullpath << "\n";
     out << "  (count = " << tex->get_ref_count()
     out << "  (count = " << tex->get_ref_count()
         << ", ram  = " << tex->get_ram_image_size()
         << ", ram  = " << tex->get_ram_image_size()
         << ", size = " << tex->get_ram_page_size()
         << ", size = " << tex->get_ram_page_size()
         << ", w = " << tex->get_x_size()
         << ", w = " << tex->get_x_size()
         << ", h = " << tex->get_y_size()
         << ", h = " << tex->get_y_size()
         << ")\n";
         << ")\n";
-    nassertv(tex->_texture_pool_key == (*ti).first);
+    nassertv(tex->_texture_pool_key == (*ti).first._fullpath);
     total_ram_size += tex->get_ram_image_size();
     total_ram_size += tex->get_ram_image_size();
     total_size += tex->get_ram_page_size();
     total_size += tex->get_ram_page_size();
   }
   }

+ 11 - 2
panda/src/gobj/texturePool.h

@@ -149,8 +149,17 @@ private:
   static TexturePool *_global_ptr;
   static TexturePool *_global_ptr;
 
 
   Mutex _lock;
   Mutex _lock;
-  typedef pmap<Filename, PT(Texture)> Textures;
-  Textures _textures;  // indexed by fullpath
+  struct LookupKey {
+    Filename _fullpath;
+    Filename _alpha_fullpath;
+    int _primary_file_num_channels = 0;
+    int _alpha_file_channel = 0;
+    Texture::TextureType _texture_type = Texture::TT_2d_texture;
+
+    INLINE bool operator < (const LookupKey &other) const;
+  };
+  typedef pmap<LookupKey, PT(Texture)> Textures;
+  Textures _textures;
   typedef pmap<Filename, Filename> RelpathLookup;
   typedef pmap<Filename, Filename> RelpathLookup;
   RelpathLookup _relpath_lookup;
   RelpathLookup _relpath_lookup;
 
 

+ 4 - 4
panda/src/linmath/coordinateSystem.h

@@ -26,10 +26,10 @@ enum CoordinateSystem {
   // turn is loaded from the config variable "coordinate-system".
   // turn is loaded from the config variable "coordinate-system".
   CS_default,
   CS_default,
 
 
-  CS_zup_right,
-  CS_yup_right,
-  CS_zup_left,
-  CS_yup_left,
+  CS_zup_right, // Z-Up, Right-handed
+  CS_yup_right, // Y-Up, Right-handed
+  CS_zup_left,  // Z-Up, Left-handed
+  CS_yup_left,  // Y-Up, Left-handed
 
 
   // CS_invalid is not a coordinate system at all.  It can be used in user-
   // CS_invalid is not a coordinate system at all.  It can be used in user-
   // input processing code to indicate a contradictory coordinate system
   // input processing code to indicate a contradictory coordinate system

+ 3 - 1
panda/src/nativenet/socket_portable.h

@@ -12,7 +12,9 @@ const int BASIC_ERROR = -1;
 // Interrogate doesn't need to parse any of this.
 // Interrogate doesn't need to parse any of this.
 
 
 typedef unsigned long SOCKET;
 typedef unsigned long SOCKET;
-typedef unsigned short sa_family_t;
+
+#include <sys/socket.h>
+#include <netinet/in.h>
 
 
 /************************************************************************
 /************************************************************************
 * HP SOCKET LIBRARY STUFF
 * HP SOCKET LIBRARY STUFF

+ 4 - 10
panda/src/pgraph/nodePath.cxx

@@ -5177,7 +5177,7 @@ get_stashed_ancestor(Thread *current_thread) const {
  */
  */
 bool NodePath::
 bool NodePath::
 operator == (const WeakNodePath &other) const {
 operator == (const WeakNodePath &other) const {
-  return _head == other._head;
+  return (other == *this);
 }
 }
 
 
 /**
 /**
@@ -5185,7 +5185,7 @@ operator == (const WeakNodePath &other) const {
  */
  */
 bool NodePath::
 bool NodePath::
 operator != (const WeakNodePath &other) const {
 operator != (const WeakNodePath &other) const {
-  return _head != other._head;
+  return (other != *this);
 }
 }
 
 
 /**
 /**
@@ -5196,7 +5196,7 @@ operator != (const WeakNodePath &other) const {
  */
  */
 bool NodePath::
 bool NodePath::
 operator < (const WeakNodePath &other) const {
 operator < (const WeakNodePath &other) const {
-  return _head < other._head;
+  return other.compare_to(*this) > 0;
 }
 }
 
 
 /**
 /**
@@ -5211,13 +5211,7 @@ operator < (const WeakNodePath &other) const {
  */
  */
 int NodePath::
 int NodePath::
 compare_to(const WeakNodePath &other) const {
 compare_to(const WeakNodePath &other) const {
-  // Nowadays, the NodePathComponents at the head are pointerwise equivalent
-  // if and only if the NodePaths are equivalent.  So we only have to compare
-  // pointers.
-  if (_head != other._head) {
-    return _head < other._head ? -1 : 1;
-  }
-  return 0;
+  return -other.compare_to(*this);
 }
 }
 
 
 /**
 /**

+ 9 - 12
panda/src/pgraph/weakNodePath.I

@@ -124,7 +124,7 @@ node() const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator == (const NodePath &other) const {
 operator == (const NodePath &other) const {
-  return _head == other._head;
+  return _head.get_orig() == other._head && !_head.was_deleted();
 }
 }
 
 
 /**
 /**
@@ -132,7 +132,7 @@ operator == (const NodePath &other) const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator != (const NodePath &other) const {
 operator != (const NodePath &other) const {
-  return _head != other._head;
+  return !operator == (other);
 }
 }
 
 
 /**
 /**
@@ -143,7 +143,7 @@ operator != (const NodePath &other) const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator < (const NodePath &other) const {
 operator < (const NodePath &other) const {
-  return _head < other._head;
+  return _head.owner_before(other._head);
 }
 }
 
 
 /**
 /**
@@ -158,8 +158,8 @@ operator < (const NodePath &other) const {
  */
  */
 INLINE int WeakNodePath::
 INLINE int WeakNodePath::
 compare_to(const NodePath &other) const {
 compare_to(const NodePath &other) const {
-  if (_head != other._head) {
-    return _head < other._head ? -1 : 1;
+  if (operator != (other)) {
+    return _head.owner_before(other._head) ? -1 : 1;
   }
   }
   return 0;
   return 0;
 }
 }
@@ -170,7 +170,7 @@ compare_to(const NodePath &other) const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator == (const WeakNodePath &other) const {
 operator == (const WeakNodePath &other) const {
-  return _head == other._head;
+  return !_head.owner_before(other._head) && !other._head.owner_before(_head);
 }
 }
 
 
 /**
 /**
@@ -178,7 +178,7 @@ operator == (const WeakNodePath &other) const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator != (const WeakNodePath &other) const {
 operator != (const WeakNodePath &other) const {
-  return _head != other._head;
+  return _head.owner_before(other._head) || other._head.owner_before(_head);
 }
 }
 
 
 /**
 /**
@@ -189,7 +189,7 @@ operator != (const WeakNodePath &other) const {
  */
  */
 INLINE bool WeakNodePath::
 INLINE bool WeakNodePath::
 operator < (const WeakNodePath &other) const {
 operator < (const WeakNodePath &other) const {
-  return _head < other._head;
+  return _head.owner_before(other._head);
 }
 }
 
 
 /**
 /**
@@ -204,10 +204,7 @@ operator < (const WeakNodePath &other) const {
  */
  */
 INLINE int WeakNodePath::
 INLINE int WeakNodePath::
 compare_to(const WeakNodePath &other) const {
 compare_to(const WeakNodePath &other) const {
-  if (_head != other._head) {
-    return _head < other._head ? -1 : 1;
-  }
-  return 0;
+  return other._head.owner_before(_head) - _head.owner_before(other._head);
 }
 }
 
 
 /**
 /**

+ 27 - 8
panda/src/pgraphnodes/shaderGenerator.cxx

@@ -252,8 +252,12 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
   // Store the material flags (not the material values itself).
   // Store the material flags (not the material values itself).
   const MaterialAttrib *material;
   const MaterialAttrib *material;
   rs->get_attrib_def(material);
   rs->get_attrib_def(material);
-  if (material->get_material() != nullptr) {
-    key._material_flags = material->get_material()->get_flags();
+  Material *mat = material->get_material();
+  if (mat != nullptr) {
+    // The next time the Material flags change, the Material should cause the
+    // states to be rehashed.
+    mat->mark_used_by_auto_shader();
+    key._material_flags = mat->get_flags();
   }
   }
 
 
   // Break out the lights by type.
   // Break out the lights by type.
@@ -731,6 +735,19 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     }
     }
   }
   }
 
 
+  bool need_color = false;
+  if (key._color_type != ColorAttrib::T_off) {
+    if (key._lighting) {
+      if (((key._material_flags & Material::F_ambient) == 0 && key._have_separate_ambient) ||
+          (key._material_flags & Material::F_diffuse) == 0 ||
+          key._calc_primary_alpha) {
+        need_color = true;
+      }
+    } else {
+      need_color = true;
+    }
+  }
+
   text << "void vshader(\n";
   text << "void vshader(\n";
   for (size_t i = 0; i < key._textures.size(); ++i) {
   for (size_t i = 0; i < key._textures.size(); ++i) {
     const ShaderKey::TextureInfo &tex = key._textures[i];
     const ShaderKey::TextureInfo &tex = key._textures[i];
@@ -794,7 +811,7 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     text << "\t out float4 l_tangent : " << tangent_freg << ",\n";
     text << "\t out float4 l_tangent : " << tangent_freg << ",\n";
     text << "\t out float4 l_binormal : " << binormal_freg << ",\n";
     text << "\t out float4 l_binormal : " << binormal_freg << ",\n";
   }
   }
-  if (key._color_type == ColorAttrib::T_vertex) {
+  if (need_color && key._color_type == ColorAttrib::T_vertex) {
     text << "\t in float4 vtx_color : " << color_vreg << ",\n";
     text << "\t in float4 vtx_color : " << color_vreg << ",\n";
     text << "\t out float4 l_color : COLOR0,\n";
     text << "\t out float4 l_color : COLOR0,\n";
   }
   }
@@ -915,7 +932,7 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
     string tcname = it->first->join("_");
     string tcname = it->first->join("_");
     text << "\t l_" << tcname << " = vtx_" << tcname << ";\n";
     text << "\t l_" << tcname << " = vtx_" << tcname << ";\n";
   }
   }
-  if (key._color_type == ColorAttrib::T_vertex) {
+  if (need_color && key._color_type == ColorAttrib::T_vertex) {
     text << "\t l_color = vtx_color;\n";
     text << "\t l_color = vtx_color;\n";
   }
   }
   if (key._texture_flags & ShaderKey::TF_map_normal) {
   if (key._texture_flags & ShaderKey::TF_map_normal) {
@@ -1015,10 +1032,12 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
   }
   }
   text << "\t out float4 o_color : COLOR0,\n";
   text << "\t out float4 o_color : COLOR0,\n";
 
 
-  if (key._color_type == ColorAttrib::T_vertex) {
-    text << "\t in float4 l_color : COLOR0,\n";
-  } else if (key._color_type == ColorAttrib::T_flat) {
-    text << "\t uniform float4 attr_color,\n";
+  if (need_color) {
+    if (key._color_type == ColorAttrib::T_vertex) {
+      text << "\t in float4 l_color : COLOR0,\n";
+    } else if (key._color_type == ColorAttrib::T_flat) {
+      text << "\t uniform float4 attr_color,\n";
+    }
   }
   }
 
 
   for (int i = 0; i < key._num_clip_planes; ++i) {
   for (int i = 0; i < key._num_clip_planes; ++i) {

+ 0 - 78
panda/src/testbed/text_test.cxx

@@ -1,78 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file text_test.cxx
- */
-
-#include "eventHandler.h"
-#include "chancfg.h"
-#include "textNode.h"
-#include "eggLoader.h"
-#include "pnotify.h"
-#include "pt_NamedNode.h"
-
-extern PT_NamedNode render;
-extern PT_NamedNode egg_root;
-extern EventHandler event_handler;
-
-extern int framework_main(int argc, char *argv[]);
-extern void (*define_keys)(EventHandler&);
-
-PT(TextNode) text_node;
-char *textStr;
-
-void event_p(CPT_Event) {
-  text_node->set_text("I'm a woo woo woo!");
-
-  nout << "text is " << text_node->get_width() << " by "
-       << text_node->get_height() << "\n";
-}
-
-void event_s(CPT_Event) {
-  text_node->set_wordwrap(5.0);
-
-  nout << "text is " << text_node->get_width() << " by "
-       << text_node->get_height() << "\n";
-}
-
-void text_keys(EventHandler& eh) {
-  eh.add_hook("p", event_p);
-  eh.add_hook("s", event_s);
-
-  text_node = new TextNode("text_node");
-  PT_NamedNode font = loader.load_sync("cmr12");
-  text_node->set_font(font.p());
-  text_node->set_wordwrap(20.0);
-  text_node->set_card_as_margin(0.25, 0.25, 0.25, 0.25);
-  PT(Texture) tex = new Texture;
-  tex->set_name("genericButton.rgb");
-  tex->set_minfilter(SamplerState::FT_linear);
-  tex->set_magfilter(SamplerState::FT_linear);
-  tex->read("/beta/toons/textures/smGreyButtonUp.rgb");
-  text_node->set_card_texture( tex );
-  text_node->set_card_border(0.1, 0.1);
-  text_node->set_text( textStr );
-  text_node->set_text_color( 0.0, 0.0, 0.0, 1.0 );
-  if (text_node->has_card_texture())
-    nout << "I've got a texture!" << "\n";
-  else
-    nout << "I don't have a texture..." << "\n";
-  nout << "text is " << text_node->get_width() << " by "
-       << text_node->get_height() << "\n";
-
-  new RenderRelation(egg_root, text_node);
-}
-
-int main(int argc, char *argv[]) {
-  define_keys = &text_keys;
-        if (argc > 1)
-                textStr = argv[1];
-        else
-                textStr = argv[0];
-  return framework_main(argc, argv);
-}

+ 42 - 0
tests/gobj/test_geom.py

@@ -0,0 +1,42 @@
+from panda3d import core
+
+empty_format = core.GeomVertexFormat.get_empty()
+
+
+def test_geom_decompose_in_place():
+    vertex_data = core.GeomVertexData("", empty_format, core.GeomEnums.UH_static)
+    prim = core.GeomTristrips(core.GeomEnums.UH_static)
+    prim.add_vertex(0)
+    prim.add_vertex(1)
+    prim.add_vertex(2)
+    prim.add_vertex(3)
+    prim.close_primitive()
+
+    geom = core.Geom(vertex_data)
+    geom.add_primitive(prim)
+
+    geom.decompose_in_place()
+
+    prim = geom.get_primitive(0)
+    assert tuple(prim.get_vertex_list()) == (0, 1, 2, 2, 1, 3)
+
+
+def test_geom_decompose():
+    vertex_data = core.GeomVertexData("", empty_format, core.GeomEnums.UH_static)
+    prim = core.GeomTristrips(core.GeomEnums.UH_static)
+    prim.add_vertex(0)
+    prim.add_vertex(1)
+    prim.add_vertex(2)
+    prim.add_vertex(3)
+    prim.close_primitive()
+
+    geom = core.Geom(vertex_data)
+    geom.add_primitive(prim)
+
+    new_geom = geom.decompose()
+
+    new_prim = new_geom.get_primitive(0)
+    assert tuple(new_prim.get_vertex_list()) == (0, 1, 2, 2, 1, 3)
+
+    # Old primitive should still be unchanged
+    assert prim == geom.get_primitive(0)

+ 196 - 0
tests/gobj/test_texture_pool.py

@@ -0,0 +1,196 @@
+from panda3d import core
+import pytest
+import tempfile
+
[email protected](scope='function')
+def pool():
+    "This fixture ensures the pool is properly emptied"
+    pool = core.TexturePool
+    pool.release_all_textures()
+    yield pool
+    pool.release_all_textures()
+
+
+def write_image(filename, channels):
+    img = core.PNMImage(1, 1, channels)
+    img.set_xel_a(0, 0, (0.0, 0.25, 0.5, 0.75))
+    assert img.write(filename)
+
+
[email protected](scope='session')
+def image_rgb_path():
+    "Generates an RGB image."
+
+    file = tempfile.NamedTemporaryFile(suffix='-rgb.png')
+    write_image(file.name, 3)
+    yield file.name
+    file.close()
+
+
[email protected](scope='session')
+def image_rgba_path():
+    "Generates an RGBA image."
+
+    file = tempfile.NamedTemporaryFile(suffix='-rgba.png')
+    write_image(file.name, 4)
+    yield file.name
+    file.close()
+
+
[email protected](scope='session')
+def image_gray_path():
+    "Generates a grayscale image."
+
+    file = tempfile.NamedTemporaryFile(suffix='-gray.png')
+    write_image(file.name, 1)
+    yield file.name
+    file.close()
+
+
+def test_load_texture_rgba(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgba4(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path, 4)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgba3(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path, 3)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 3
+
+
+def test_load_texture_rgba2(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path, 2)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 2
+
+
+def test_load_texture_rgba1(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path, 1)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 1
+
+
+def test_load_texture_rgb(pool, image_rgb_path):
+    tex = pool.load_texture(image_rgb_path)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 3
+
+
+def test_load_texture_rgb4(pool, image_rgb_path):
+    # Will not increase this
+    tex = pool.load_texture(image_rgb_path, 4)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 3
+
+
+def test_load_texture_rgb3(pool, image_rgb_path):
+    tex = pool.load_texture(image_rgb_path, 3)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 3
+
+
+def test_load_texture_rgb2(pool, image_rgb_path):
+    # Cannot reduce this, since it would add an alpha channel
+    tex = pool.load_texture(image_rgb_path, 2)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 3
+
+
+def test_load_texture_rgb1(pool, image_rgb_path):
+    tex = pool.load_texture(image_rgb_path, 1)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 1
+
+
+def test_load_texture_rgba_alpha(pool, image_rgba_path, image_gray_path):
+    tex = pool.load_texture(image_rgba_path, image_gray_path)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgba4_alpha(pool, image_rgba_path, image_gray_path):
+    tex = pool.load_texture(image_rgba_path, image_gray_path, 4)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgba3_alpha(pool, image_rgba_path, image_gray_path):
+    tex = pool.load_texture(image_rgba_path, image_gray_path, 3)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgba2_alpha(pool, image_rgba_path, image_gray_path):
+    #FIXME: why is this not consistent with test_load_texture_rgb2_alpha?
+    tex = pool.load_texture(image_rgba_path, image_gray_path, 2)
+    assert tex.num_components == 2
+
+
+def test_load_texture_rgba1_alpha(pool, image_rgba_path, image_gray_path):
+    tex = pool.load_texture(image_rgba_path, image_gray_path, 1)
+    assert tex.num_components == 2
+
+
+def test_load_texture_rgb_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path, image_gray_path)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgb4_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path, image_gray_path, 4)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgb3_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path, image_gray_path, 3)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgb2_alpha(pool, image_rgb_path, image_gray_path):
+    #FIXME: why is this not consistent with test_load_texture_rgba2_alpha?
+    tex = pool.load_texture(image_rgb_path, image_gray_path, 2)
+    assert tex.num_components == 4
+
+
+def test_load_texture_rgb1_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path, image_gray_path, 1)
+    assert tex.num_components == 2
+
+
+def test_reload_texture_fewer_channels(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 4
+
+    tex = pool.load_texture(image_rgba_path, 3)
+    assert tex.num_components == 3
+
+
+def test_reload_texture_more_channels(pool, image_rgba_path):
+    tex = pool.load_texture(image_rgba_path, 3)
+    assert pool.has_texture(image_rgba_path)
+    assert tex.num_components == 3
+
+    tex = pool.load_texture(image_rgba_path)
+    assert tex.num_components == 4
+
+
+def test_reload_texture_with_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path)
+    assert pool.has_texture(image_rgb_path)
+    assert tex.num_components == 3
+
+    tex = pool.load_texture(image_rgb_path, image_gray_path)
+    assert tex.num_components == 4
+
+
+def test_reload_texture_without_alpha(pool, image_rgb_path, image_gray_path):
+    tex = pool.load_texture(image_rgb_path, image_gray_path)
+    assert tex.num_components == 4
+
+    tex = pool.load_texture(image_rgb_path)
+    assert tex.num_components == 3

Some files were not shown because too many files changed in this diff