Browse Source

Merge branch 'master' into cmake

Sam Edwards 7 years ago
parent
commit
df805bd02b
100 changed files with 732 additions and 190 deletions
  1. 1 1
      direct/src/dcparser/dcClass.cxx
  2. 13 13
      direct/src/filter/CommonFilters.py
  3. 2 2
      direct/src/filter/FilterManager.py
  4. 10 2
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  5. 1 1
      dtool/src/prc/notifyCategory.cxx
  6. 18 0
      dtool/src/prc/streamWrapper.I
  7. 10 0
      dtool/src/prc/streamWrapper.h
  8. 8 1
      makepanda/makepanda.py
  9. 1 0
      panda/src/audiotraits/openalAudioManager.cxx
  10. 3 0
      panda/src/chan/animBundle.h
  11. 2 0
      panda/src/chan/animBundleNode.h
  12. 2 0
      panda/src/chan/animChannelMatrixDynamic.h
  13. 1 1
      panda/src/chan/animChannelMatrixXfmTable.cxx
  14. 2 0
      panda/src/chan/animChannelMatrixXfmTable.h
  15. 22 0
      panda/src/chan/animChannelScalarDynamic.I
  16. 5 8
      panda/src/chan/animChannelScalarDynamic.cxx
  17. 5 0
      panda/src/chan/animChannelScalarDynamic.h
  18. 1 1
      panda/src/chan/animChannelScalarTable.cxx
  19. 2 0
      panda/src/chan/animChannelScalarTable.h
  20. 8 0
      panda/src/display/displayRegion.I
  21. 24 5
      panda/src/display/displayRegion.cxx
  22. 3 0
      panda/src/display/displayRegion.h
  23. 42 12
      panda/src/display/graphicsEngine.cxx
  24. 9 0
      panda/src/display/graphicsOutput.I
  25. 27 2
      panda/src/display/graphicsOutput.cxx
  26. 2 0
      panda/src/display/graphicsOutput.h
  27. 1 2
      panda/src/display/graphicsStateGuardian.cxx
  28. 0 1
      panda/src/display/graphicsStateGuardian.h
  29. 2 1
      panda/src/egg/eggGroup.cxx
  30. 2 1
      panda/src/egg/eggVertexPool.cxx
  31. 2 1
      panda/src/egg/eggXfmAnimData.cxx
  32. 2 1
      panda/src/egg/eggXfmSAnim.cxx
  33. 1 1
      panda/src/event/asyncFuture.cxx
  34. 2 2
      panda/src/event/pointerEvent.cxx
  35. 4 1
      panda/src/express/multifile.cxx
  36. 1 1
      panda/src/express/patchfile.cxx
  37. 13 0
      panda/src/express/subStreamBuf.cxx
  38. 1 0
      panda/src/express/subStreamBuf.h
  39. 17 10
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  40. 8 1
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  41. 3 0
      panda/src/ffmpeg/ffmpegVirtualFile.cxx
  42. 1 1
      panda/src/glstuff/glGeomContext_src.cxx
  43. 6 2
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  44. 24 0
      panda/src/glstuff/glGraphicsStateGuardian_src.I
  45. 142 13
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  46. 11 0
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  47. 5 0
      panda/src/glstuff/glShaderContext_src.cxx
  48. 7 1
      panda/src/glxdisplay/glxGraphicsBuffer.cxx
  49. 6 1
      panda/src/glxdisplay/glxGraphicsPixmap.cxx
  50. 8 0
      panda/src/glxdisplay/glxGraphicsStateGuardian.cxx
  51. 33 0
      panda/src/glxdisplay/glxGraphicsWindow.cxx
  52. 1 0
      panda/src/glxdisplay/glxGraphicsWindow.h
  53. 6 3
      panda/src/gobj/geom.cxx
  54. 1 1
      panda/src/gobj/geomCacheManager.cxx
  55. 4 4
      panda/src/gobj/geomPrimitive.cxx
  56. 1 1
      panda/src/gobj/geomTrifans.cxx
  57. 1 1
      panda/src/gobj/geomVertexArrayData.cxx
  58. 2 1
      panda/src/gobj/geomVertexData.cxx
  59. 1 1
      panda/src/gobj/shader.cxx
  60. 8 5
      panda/src/gobj/texture.cxx
  61. 36 6
      panda/src/grutil/sceneGraphAnalyzerMeter.cxx
  62. 3 0
      panda/src/grutil/sceneGraphAnalyzerMeter.h
  63. 1 1
      panda/src/gsgbase/graphicsStateGuardianBase.cxx
  64. 3 0
      panda/src/gsgbase/graphicsStateGuardianBase.h
  65. 5 1
      panda/src/movies/flacAudioCursor.cxx
  66. 1 0
      panda/src/movies/flacAudioCursor.h
  67. 3 3
      panda/src/nativenet/socket_address.I
  68. 2 2
      panda/src/nativenet/socket_address.cxx
  69. 2 1
      panda/src/net/connection.cxx
  70. 2 1
      panda/src/net/datagramTCPHeader.cxx
  71. 1 1
      panda/src/parametrics/parametricCurveCollection.cxx
  72. 4 4
      panda/src/pgraph/clipPlaneAttrib.cxx
  73. 1 1
      panda/src/pgraph/cullBinManager.cxx
  74. 3 0
      panda/src/pgraph/cullResult.cxx
  75. 4 4
      panda/src/pgraph/lightAttrib.cxx
  76. 6 3
      panda/src/pgraph/nodePath.cxx
  77. 2 1
      panda/src/pgraph/pandaNode.cxx
  78. 1 1
      panda/src/pgraph/renderAttribRegistry.cxx
  79. 2 1
      panda/src/pgraph/sceneGraphReducer.cxx
  80. 1 1
      panda/src/pgraphnodes/ambientLight.cxx
  81. 2 1
      panda/src/pipeline/pythonThread.cxx
  82. 16 8
      panda/src/pnmimage/pfmFile.cxx
  83. 10 8
      panda/src/pnmimage/pnmImage.cxx
  84. 0 23
      panda/src/pnmimage/pnmImageHeader.I
  85. 3 2
      panda/src/pnmimage/pnmImageHeader.h
  86. 2 1
      panda/src/pnmimagetypes/pnmFileTypePfm.cxx
  87. 2 1
      panda/src/pnmimagetypes/pnmFileTypeSGIWriter.cxx
  88. 1 1
      panda/src/putil/sparseArray.I
  89. 1 1
      panda/src/text/textAssembler.cxx
  90. 0 2
      panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
  91. 2 1
      panda/src/vision/webcamVideoCursorV4L.cxx
  92. 5 0
      panda/src/wgldisplay/wglGraphicsWindow.cxx
  93. 5 0
      panda/src/x11display/config_x11display.cxx
  94. 1 0
      panda/src/x11display/config_x11display.h
  95. 6 0
      panda/src/x11display/x11GraphicsPipe.cxx
  96. 17 0
      panda/src/x11display/x11GraphicsWindow.cxx
  97. 2 0
      panda/src/x11display/x11GraphicsWindow.h
  98. 1 1
      pandatool/src/assimp/pandaIOSystem.cxx
  99. 1 1
      pandatool/src/eggcharbase/eggBackPointer.cxx
  100. 21 0
      tests/pnmimage/test_pnmimage.py

+ 1 - 1
direct/src/dcparser/dcClass.cxx

@@ -1240,7 +1240,7 @@ shadow_inherited_field(const string &name) {
   }
 
   // If we get here, the named field wasn't in the list.  Huh.
-  nassertv(false);
+  nassert_raise("named field not in list");
 }
 
 /**

+ 13 - 13
direct/src/filter/CommonFilters.py

@@ -184,8 +184,8 @@ class CommonFilters:
             if ("BlurSharpen" in configuration):
                 blur0=self.textures["blur0"]
                 blur1=self.textures["blur1"]
-                self.blur.append(self.manager.renderQuadInto(colortex=blur0,div=2))
-                self.blur.append(self.manager.renderQuadInto(colortex=blur1))
+                self.blur.append(self.manager.renderQuadInto("filter-blur0", colortex=blur0,div=2))
+                self.blur.append(self.manager.renderQuadInto("filter-blur1", colortex=blur1))
                 self.blur[0].setShaderInput("src", self.textures["color"])
                 self.blur[0].setShader(self.loadShader("filter-blurx.sha"))
                 self.blur[1].setShaderInput("src", blur0)
@@ -195,9 +195,9 @@ class CommonFilters:
                 ssao0=self.textures["ssao0"]
                 ssao1=self.textures["ssao1"]
                 ssao2=self.textures["ssao2"]
-                self.ssao.append(self.manager.renderQuadInto(colortex=ssao0))
-                self.ssao.append(self.manager.renderQuadInto(colortex=ssao1,div=2))
-                self.ssao.append(self.manager.renderQuadInto(colortex=ssao2))
+                self.ssao.append(self.manager.renderQuadInto("filter-ssao0", colortex=ssao0))
+                self.ssao.append(self.manager.renderQuadInto("filter-ssao1", colortex=ssao1,div=2))
+                self.ssao.append(self.manager.renderQuadInto("filter-ssao2", colortex=ssao2))
                 self.ssao[0].setShaderInput("depth", self.textures["depth"])
                 self.ssao[0].setShaderInput("normal", self.textures["aux"])
                 self.ssao[0].setShaderInput("random", loader.loadTexture("maps/random.rgb"))
@@ -215,21 +215,21 @@ class CommonFilters:
                 bloom3=self.textures["bloom3"]
                 if (bloomconf.size == "large"):
                     scale=8
-                    downsampler="filter-down4.sha"
+                    downsampler="filter-down4"
                 elif (bloomconf.size == "medium"):
                     scale=4
-                    downsampler="filter-copy.sha"
+                    downsampler="filter-copy"
                 else:
                     scale=2
-                    downsampler="filter-copy.sha"
-                self.bloom.append(self.manager.renderQuadInto(colortex=bloom0, div=2,     align=scale))
-                self.bloom.append(self.manager.renderQuadInto(colortex=bloom1, div=scale, align=scale))
-                self.bloom.append(self.manager.renderQuadInto(colortex=bloom2, div=scale, align=scale))
-                self.bloom.append(self.manager.renderQuadInto(colortex=bloom3, div=scale, align=scale))
+                    downsampler="filter-copy"
+                self.bloom.append(self.manager.renderQuadInto("filter-bloomi", colortex=bloom0, div=2,     align=scale))
+                self.bloom.append(self.manager.renderQuadInto(downsampler, colortex=bloom1, div=scale, align=scale))
+                self.bloom.append(self.manager.renderQuadInto("filter-bloomx", colortex=bloom2, div=scale, align=scale))
+                self.bloom.append(self.manager.renderQuadInto("filter-bloomy", colortex=bloom3, div=scale, align=scale))
                 self.bloom[0].setShaderInput("src", self.textures["color"])
                 self.bloom[0].setShader(self.loadShader("filter-bloomi.sha"))
                 self.bloom[1].setShaderInput("src", bloom0)
-                self.bloom[1].setShader(self.loadShader(downsampler))
+                self.bloom[1].setShader(self.loadShader(downsampler + ".sha"))
                 self.bloom[2].setShaderInput("src", bloom1)
                 self.bloom[2].setShader(self.loadShader("filter-bloomx.sha"))
                 self.bloom[3].setShaderInput("src", bloom2)

+ 2 - 2
direct/src/filter/FilterManager.py

@@ -236,7 +236,7 @@ class FilterManager(DirectObject):
 
         return quad
 
-    def renderQuadInto(self, mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
+    def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
 
         """ Creates an offscreen buffer for an intermediate
         computation. Installs a quad into the buffer.  Returns
@@ -250,7 +250,7 @@ class FilterManager(DirectObject):
 
         depthbits = bool(depthtex != None)
 
-        buffer = self.createBuffer("filter-stage", winx, winy, texgroup, depthbits)
+        buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
 
         if (buffer == None):
             return None

+ 10 - 2
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -6975,7 +6975,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
     out << "  if (wrap != nullptr) {\n"
            "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
     if (!property->_setter_remaps.empty()) {
-      out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
+      if (property->_has_this) {
+        out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
+      } else {
+        out << "    {\n";
+      }
       out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
       out << "    }\n";
     }
@@ -7006,7 +7010,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
         "    wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
         "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
       if (!property->_setter_remaps.empty()) {
-        out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
+        if (property->_has_this) {
+          out << "    if (!DtoolInstance_IS_CONST(self)) {\n";
+        } else {
+          out << "    {\n";
+        }
         out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
         if (property->_inserter != nullptr) {
           out << "      wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";

+ 1 - 1
dtool/src/prc/notifyCategory.cxx

@@ -110,7 +110,7 @@ out(NotifySeverity severity, bool prefix) const {
       nout << *this << "(" << severity << "): ";
     }
     if (assert_abort) {
-      nassertr(false, nout);
+      nassert_raise("unprotected debug statement");
     }
 
     return nout;

+ 18 - 0
dtool/src/prc/streamWrapper.I

@@ -58,6 +58,24 @@ release() {
   _lock.unlock();
 }
 
+/**
+ * Increments the reference count.  Only has impact if the class that manages
+ * this StreamWrapper's lifetime (eg. Multifile) respects it.
+ */
+INLINE void StreamWrapperBase::
+ref() const {
+  AtomicAdjust::inc(_ref_count);
+}
+
+/**
+ * Decrements the reference count.  Only has impact if the class that manages
+ * this StreamWrapper's lifetime (eg. Multifile) respects it.
+ */
+INLINE bool StreamWrapperBase::
+unref() const {
+  return AtomicAdjust::dec(_ref_count);
+}
+
 /**
  *
  */

+ 10 - 0
dtool/src/prc/streamWrapper.h

@@ -16,6 +16,7 @@
 
 #include "dtoolbase.h"
 #include "mutexImpl.h"
+#include "atomicAdjust.h"
 
 /**
  * The base class for both IStreamWrapper and OStreamWrapper, this provides
@@ -30,8 +31,17 @@ PUBLISHED:
   INLINE void acquire();
   INLINE void release();
 
+public:
+  INLINE void ref() const;
+  INLINE bool unref() const;
+
 private:
   MutexImpl _lock;
+
+  // This isn't really designed as a reference counted class, but it is useful
+  // to treat it as one when dealing with substreams created by Multifile.
+  mutable AtomicAdjust::Integer _ref_count = 1;
+
 #ifdef SIMPLE_THREADS
   // In the SIMPLE_THREADS case, we need to use a bool flag, because MutexImpl
   // defines to nothing in this case--but we still need to achieve a form of

+ 8 - 1
makepanda/makepanda.py

@@ -828,7 +828,7 @@ if (COMPILER=="GCC"):
         SmartPkgEnable("EIGEN",     "eigen3",    (), ("Eigen/Dense",), target_pkg = 'ALWAYS')
         SmartPkgEnable("ARTOOLKIT", "",          ("AR"), "AR/ar.h")
         SmartPkgEnable("FCOLLADA",  "",          ChooseLib(fcollada_libs, "FCOLLADA"), ("FCollada", "FCollada/FCollada.h"))
-        SmartPkgEnable("ASSIMP",    "",          ("assimp"), "assimp/Importer.hpp")
+        SmartPkgEnable("ASSIMP",    "assimp",    ("assimp"), "assimp/Importer.hpp")
         SmartPkgEnable("FFMPEG",    ffmpeg_libs, ffmpeg_libs, ("libavformat/avformat.h", "libavcodec/avcodec.h", "libavutil/avutil.h"))
         SmartPkgEnable("SWSCALE",   "libswscale", "libswscale", ("libswscale/swscale.h"), target_pkg = "FFMPEG", thirdparty_dir = "ffmpeg")
         SmartPkgEnable("SWRESAMPLE","libswresample", "libswresample", ("libswresample/swresample.h"), target_pkg = "FFMPEG", thirdparty_dir = "ffmpeg")
@@ -872,6 +872,13 @@ if (COMPILER=="GCC"):
         else:
             PkgDisable("OPENCV")
 
+        if not PkgSkip("ASSIMP") and \
+            os.path.isfile(GetThirdpartyDir() + "assimp/lib/libassimp.a"):
+            # Also pick up IrrXML, which is needed when linking statically.
+            irrxml = GetThirdpartyDir() + "assimp/lib/libIrrXML.a"
+            if os.path.isfile(irrxml):
+                LibName("ASSIMP", irrxml)
+
         rocket_libs = ("RocketCore", "RocketControls")
         if (GetOptimize() <= 3):
             rocket_libs += ("RocketDebugger",)

+ 1 - 0
panda/src/audiotraits/openalAudioManager.cxx

@@ -553,6 +553,7 @@ uncache_sound(const Filename &file_name) {
       if (sd->_movie->get_filename() == path ||
           sd->_movie->get_filename() == file_name) {
         exqi = _expiring_streams.erase(exqi);
+        delete sd;
         continue;
       }
     }

+ 3 - 0
panda/src/chan/animBundle.h

@@ -38,6 +38,9 @@ PUBLISHED:
   INLINE double get_base_frame_rate() const;
   INLINE int get_num_frames() const;
 
+  MAKE_PROPERTY(base_frame_rate, get_base_frame_rate);
+  MAKE_PROPERTY(num_frames, get_num_frames);
+
   virtual void output(std::ostream &out) const;
 
 protected:

+ 2 - 0
panda/src/chan/animBundleNode.h

@@ -41,6 +41,8 @@ public:
 PUBLISHED:
   INLINE AnimBundle *get_bundle() const;
 
+  MAKE_PROPERTY(bundle, get_bundle);
+
   static AnimBundle *find_anim_bundle(PandaNode *root);
 
 private:

+ 2 - 0
panda/src/chan/animChannelMatrixDynamic.h

@@ -57,6 +57,8 @@ PUBLISHED:
   INLINE const TransformState *get_value_transform() const;
   INLINE PandaNode *get_value_node() const;
 
+  MAKE_PROPERTY(value_node, get_value_node, set_value_node);
+
 protected:
   virtual AnimGroup *make_copy(AnimGroup *parent) const;
 

+ 1 - 1
panda/src/chan/animChannelMatrixXfmTable.cxx

@@ -239,7 +239,7 @@ set_table(char table_id, const CPTA_stdfloat &table) {
   if (table.size() > 1 && (int)table.size() < num_frames) {
     // The new table has an invalid number of frames--it doesn't match the
     // bundle's requirement.
-    nassertv(false);
+    nassert_raise("mismatched number of frames");
     return;
   }
 

+ 2 - 0
panda/src/chan/animChannelMatrixXfmTable.h

@@ -59,6 +59,8 @@ PUBLISHED:
   INLINE bool has_table(char table_id) const;
   INLINE void clear_table(char table_id);
 
+  MAKE_MAP_PROPERTY(tables, has_table, get_table, set_table, clear_table);
+
 public:
   virtual void write(std::ostream &out, int indent_level) const;
 

+ 22 - 0
panda/src/chan/animChannelScalarDynamic.I

@@ -10,3 +10,25 @@
  * @author drose
  * @date 2003-10-20
  */
+
+/**
+ * Gets the value of the channel.  This will return the value explicitly
+ * specified by set_value() unless a value node was specified using
+ * set_value_node().
+ */
+INLINE PN_stdfloat AnimChannelScalarDynamic::
+get_value() const {
+  if (_value_node != nullptr) {
+    return _value->get_pos()[0];
+  } else {
+    return _float_value;
+  }
+}
+
+/**
+ * Returns the node that was set via set_value_node(), if any.
+ */
+INLINE PandaNode *AnimChannelScalarDynamic::
+get_value_node() const {
+  return _value_node;
+}

+ 5 - 8
panda/src/chan/animChannelScalarDynamic.cxx

@@ -84,16 +84,12 @@ has_changed(int, double, int, double) {
  */
 void AnimChannelScalarDynamic::
 get_value(int, PN_stdfloat &value) {
-  if (_value_node != nullptr) {
-    value = _value->get_pos()[0];
-
-  } else {
-    value = _float_value;
-  }
+  value = get_value();
 }
 
 /**
- * Explicitly sets the value.
+ * Explicitly sets the value.  This will remove any node assigned via
+ * set_value_node().
  */
 void AnimChannelScalarDynamic::
 set_value(PN_stdfloat value) {
@@ -104,7 +100,8 @@ set_value(PN_stdfloat value) {
 
 /**
  * Specifies a node whose transform will be queried each frame to implicitly
- * specify the transform of this joint.
+ * specify the transform of this joint.  This will override the values set by
+ * set_value().
  */
 void AnimChannelScalarDynamic::
 set_value_node(PandaNode *value_node) {

+ 5 - 0
panda/src/chan/animChannelScalarDynamic.h

@@ -41,11 +41,16 @@ public:
   virtual bool has_changed(int last_frame, double last_frac,
                            int this_frame, double this_frac);
   virtual void get_value(int frame, PN_stdfloat &value);
+  INLINE PN_stdfloat get_value() const;
+  INLINE PandaNode *get_value_node() const;
 
 PUBLISHED:
   void set_value(PN_stdfloat value);
   void set_value_node(PandaNode *node);
 
+  MAKE_PROPERTY(value, get_value, set_value);
+  MAKE_PROPERTY(value_node, get_value_node, set_value_node);
+
 protected:
   virtual AnimGroup *make_copy(AnimGroup *parent) const;
 

+ 1 - 1
panda/src/chan/animChannelScalarTable.cxx

@@ -104,7 +104,7 @@ set_table(const CPTA_stdfloat &table) {
   if (table.size() > 1 && (int)table.size() < num_frames) {
     // The new table has an invalid number of frames--it doesn't match the
     // bundle's requirement.
-    nassertv(false);
+    nassert_raise("mismatched number of frames");
     return;
   }
 

+ 2 - 0
panda/src/chan/animChannelScalarTable.h

@@ -44,6 +44,8 @@ PUBLISHED:
   INLINE bool has_table() const;
   INLINE void clear_table();
 
+  MAKE_PROPERTY2(table, has_table, get_table, set_table, clear_table);
+
 public:
   virtual void write(std::ostream &out, int indent_level) const;
 

+ 8 - 0
panda/src/display/displayRegion.I

@@ -523,6 +523,14 @@ get_draw_region_pcollector() {
   return _draw_region_pcollector;
 }
 
+/**
+ * Returns a unique name used for debugging.
+ */
+INLINE const std::string &DisplayRegion::
+get_debug_name() const {
+  return _debug_name;
+}
+
 /**
  *
  */

+ 24 - 5
panda/src/display/displayRegion.cxx

@@ -677,13 +677,32 @@ do_compute_pixels(int i, int x_size, int y_size, CData *cdata) {
  */
 void DisplayRegion::
 set_active_index(int index) {
-#ifdef DO_PSTATS
+#if defined(DO_PSTATS) || !defined(NDEBUG)
   std::ostringstream strm;
-  strm << "dr_" << index;
-  string name = strm.str();
 
-  _cull_region_pcollector = PStatCollector(_window->get_cull_window_pcollector(), name);
-  _draw_region_pcollector = PStatCollector(_window->get_draw_window_pcollector(), name);
+  // To make a more useful name for PStats and debug output, we add the scene
+  // graph name and camera name.
+  NodePath camera = get_camera();
+  if (!camera.is_empty()) {
+    Camera *camera_node = DCAST(Camera, camera.node());
+    if (camera_node != nullptr) {
+      NodePath scene_root = camera_node->get_scene();
+      if (scene_root.is_empty()) {
+        scene_root = camera.get_top();
+      }
+      strm << scene_root.get_name();
+    }
+  }
+
+  // And add the index in case we have two scene graphs with the same name.
+  strm << "#" << index;
+
+  _debug_name = strm.str();
+#endif
+
+#ifdef DO_PSTATS
+  _cull_region_pcollector = PStatCollector(_window->get_cull_window_pcollector(), _debug_name);
+  _draw_region_pcollector = PStatCollector(_window->get_draw_window_pcollector(), _debug_name);
 #endif  // DO_PSTATS
 }
 

+ 3 - 0
panda/src/display/displayRegion.h

@@ -184,6 +184,8 @@ public:
   INLINE PStatCollector &get_cull_region_pcollector();
   INLINE PStatCollector &get_draw_region_pcollector();
 
+  INLINE const std::string &get_debug_name() const;
+
   struct Region {
     INLINE Region();
 
@@ -277,6 +279,7 @@ private:
 
   PStatCollector _cull_region_pcollector;
   PStatCollector _draw_region_pcollector;
+  std::string _debug_name;
 
 public:
   static TypeHandle get_class_type() {

+ 42 - 12
panda/src/display/graphicsEngine.cxx

@@ -47,6 +47,7 @@
 #include "displayRegionCullCallbackData.h"
 #include "displayRegionDrawCallbackData.h"
 #include "callbackGraphicsWindow.h"
+#include "depthTestAttrib.h"
 
 #if defined(WIN32)
   #define WINDOWS_LEAN_AND_MEAN
@@ -1174,7 +1175,8 @@ extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) {
  */
 void GraphicsEngine::
 dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg) {
-  nassertv(sattr->get_shader() != nullptr);
+  const Shader *shader = sattr->get_shader();
+  nassertv(shader != nullptr);
   nassertv(gsg != nullptr);
 
   ReMutexHolder holder(_lock);
@@ -1184,8 +1186,10 @@ dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, Graph
   string draw_name = gsg->get_threading_model().get_draw_name();
   if (draw_name.empty()) {
     // A single-threaded environment.  No problem.
+    gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename());
     gsg->set_state_and_transform(state, TransformState::make_identity());
     gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
+    gsg->pop_group_marker();
 
   } else {
     // A multi-threaded environment.  We have to wait until the draw thread
@@ -1431,7 +1435,13 @@ cull_and_draw_together(GraphicsEngine::Windows wlist,
       }
 
       if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
-        win->clear(current_thread);
+        if (win->is_any_clear_active()) {
+          GraphicsStateGuardian *gsg = win->get_gsg();
+          PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
+          gsg->push_group_marker("Clear");
+          win->clear(current_thread);
+          gsg->pop_group_marker();
+        }
 
         int num_display_regions = win->get_num_active_display_regions();
         for (int i = 0; i < num_display_regions; i++) {
@@ -1468,6 +1478,8 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
   GraphicsStateGuardian *gsg = win->get_gsg();
   nassertv(gsg != nullptr);
 
+  gsg->push_group_marker(dr->get_debug_name());
+
   PT(SceneSetup) scene_setup;
 
   {
@@ -1476,6 +1488,7 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
     gsg->prepare_display_region(&dr_reader);
 
     if (dr_reader.is_any_clear_active()) {
+      PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
       gsg->clear(dr);
     }
 
@@ -1512,6 +1525,8 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
       gsg->end_scene();
     }
   }
+
+  gsg->pop_group_marker();
 }
 
 /**
@@ -1651,7 +1666,12 @@ draw_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
         // a current context for PStatGPUTimer to work.
         {
           PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
-          win->clear(current_thread);
+          if (win->is_any_clear_active()) {
+            PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
+            win->get_gsg()->push_group_marker("Clear");
+            win->clear(current_thread);
+            win->get_gsg()->pop_group_marker();
+          }
 
           if (display_cat.is_spam()) {
             display_cat.spam()
@@ -2000,6 +2020,8 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
   // Statistics
   PStatGPUTimer timer(gsg, dr->get_draw_region_pcollector(), current_thread);
 
+  gsg->push_group_marker(dr->get_debug_name());
+
   PT(CullResult) cull_result;
   PT(SceneSetup) scene_setup;
   {
@@ -2015,6 +2037,7 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
     win->change_scenes(&dr_reader);
     gsg->prepare_display_region(&dr_reader);
     if (dr_reader.is_any_clear_active()) {
+      PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
       gsg->clear(dr_reader.get_object());
     }
 
@@ -2024,9 +2047,12 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
   if (cbobj != nullptr) {
     // Issue the draw callback on this DisplayRegion.
 
-    // Set the GSG to the initial state.
+    // Set the GSG to the initial state.  We disable depth testing since that
+    // is the default OpenGL state, and some libraries (eg. Kivy) expect that.
+    static CPT(RenderState) state = RenderState::make(
+      DepthTestAttrib::make(DepthTestAttrib::M_none));
     gsg->clear_before_callback();
-    gsg->set_state_and_transform(RenderState::make_empty(), TransformState::make_identity());
+    gsg->set_state_and_transform(state, TransformState::make_identity());
 
     DisplayRegionDrawCallbackData cbdata(cull_result, scene_setup);
     cbobj->do_callback(&cbdata);
@@ -2034,11 +2060,7 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
     // We don't trust the state the callback may have left us in.
     gsg->clear_state_and_transform();
 
-    // The callback has taken care of the drawing.
-    return;
-  }
-
-  if (cull_result == nullptr || scene_setup == nullptr) {
+  } else if (cull_result == nullptr || scene_setup == nullptr) {
     // Nothing to see here.
 
   } else if (dr->is_stereo()) {
@@ -2059,6 +2081,8 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
       gsg->end_scene();
     }
   }
+
+  gsg->pop_group_marker();
 }
 
 /**
@@ -2668,8 +2692,14 @@ thread_main() {
 
     case TS_do_compute:
       nassertd(_gsg != nullptr && _state != nullptr) break;
-      _gsg->set_state_and_transform(_state, TransformState::make_identity());
-      _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
+      {
+        const ShaderAttrib *sattr;
+        _state->get_attrib(sattr);
+        _gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename());
+        _gsg->set_state_and_transform(_state, TransformState::make_identity());
+        _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
+        _gsg->pop_group_marker();
+      }
       break;
 
     case TS_do_extract:

+ 9 - 0
panda/src/display/graphicsOutput.I

@@ -703,6 +703,15 @@ get_draw_window_pcollector() {
   return _draw_window_pcollector;
 }
 
+/**
+ * Returns a PStatCollector for timing the clear operation for just this
+ * GraphicsOutput.
+ */
+INLINE PStatCollector &GraphicsOutput::
+get_clear_window_pcollector() {
+  return _clear_window_pcollector;
+}
+
 /**
  * Display the spam message associated with begin_frame
  */

+ 27 - 2
panda/src/display/graphicsOutput.cxx

@@ -77,6 +77,7 @@ GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
   _lock("GraphicsOutput"),
   _cull_window_pcollector(_cull_pcollector, name),
   _draw_window_pcollector(_draw_pcollector, name),
+  _clear_window_pcollector(_draw_window_pcollector, "Clear"),
   _size(0, 0)
 {
 #ifdef DO_MEMORY_USAGE
@@ -411,15 +412,39 @@ is_active() const {
     return false;
   }
 
-  CDReader cdata(_cycler);
+  CDLockedReader cdata(_cycler);
+  if (!cdata->_active) {
+    return false;
+  }
+
   if (cdata->_one_shot_frame != -1) {
     // If one_shot is in effect, then we are active only for the one indicated
     // frame.
     if (cdata->_one_shot_frame != ClockObject::get_global_clock()->get_frame_count()) {
       return false;
+    } else {
+      return true;
     }
   }
-  return cdata->_active;
+
+  // If the window has a clear value set, it is active.
+  if (is_any_clear_active()) {
+    return true;
+  }
+
+  // If we triggered a copy operation, it is also active.
+  if (_trigger_copy) {
+    return true;
+  }
+
+  // The window is active if at least one display region is active.
+  if (cdata->_active_display_regions_stale) {
+    CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, false);
+    ((GraphicsOutput *)this)->do_determine_display_regions(cdataw);
+    return !cdataw->_active_display_regions.empty();
+  } else {
+    return !cdata->_active_display_regions.empty();
+  }
 }
 
 /**

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

@@ -289,6 +289,7 @@ public:
 
   INLINE PStatCollector &get_cull_window_pcollector();
   INLINE PStatCollector &get_draw_window_pcollector();
+  INLINE PStatCollector &get_clear_window_pcollector();
 
 protected:
   virtual void pixel_factor_changed();
@@ -409,6 +410,7 @@ protected:
   static PStatCollector _draw_pcollector;
   PStatCollector _cull_window_pcollector;
   PStatCollector _draw_window_pcollector;
+  PStatCollector _clear_window_pcollector;
 
 public:
   static TypeHandle get_class_type() {

+ 1 - 2
panda/src/display/graphicsStateGuardian.cxx

@@ -92,7 +92,6 @@ PStatCollector GraphicsStateGuardian::_transform_state_pcollector("State changes
 PStatCollector GraphicsStateGuardian::_texture_state_pcollector("State changes:Textures");
 PStatCollector GraphicsStateGuardian::_draw_primitive_pcollector("Draw:Primitive:Draw");
 PStatCollector GraphicsStateGuardian::_draw_set_state_pcollector("Draw:Set State");
-PStatCollector GraphicsStateGuardian::_clear_pcollector("Draw:Clear");
 PStatCollector GraphicsStateGuardian::_flush_pcollector("Draw:Flush");
 PStatCollector GraphicsStateGuardian::_compute_dispatch_pcollector("Draw:Compute dispatch");
 
@@ -744,7 +743,7 @@ issue_timer_query(int pstats_index) {
  */
 void GraphicsStateGuardian::
 dispatch_compute(int num_groups_x, int num_groups_y, int num_groups_z) {
-  nassertv(false /* Compute shaders not supported by GSG */);
+  nassert_raise("Compute shaders not supported by GSG");
 }
 
 /**

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

@@ -685,7 +685,6 @@ public:
   static PStatCollector _texture_state_pcollector;
   static PStatCollector _draw_primitive_pcollector;
   static PStatCollector _draw_set_state_pcollector;
-  static PStatCollector _clear_pcollector;
   static PStatCollector _flush_pcollector;
   static PStatCollector _compute_dispatch_pcollector;
   static PStatCollector _wait_occlusion_pcollector;

+ 2 - 1
panda/src/egg/eggGroup.cxx

@@ -192,7 +192,8 @@ write(ostream &out, int indent_level) const {
 
   default:
     // invalid group type
-    nassertv(false);
+    nassert_raise("invalid EggGroup type");
+    return;
   }
 
   if (is_of_type(EggBin::get_class_type())) {

+ 2 - 1
panda/src/egg/eggVertexPool.cxx

@@ -445,7 +445,8 @@ add_vertex(EggVertex *vertex, int index) {
     }
 
     // Oops, you duplicated a vertex index.
-    nassertr(false, nullptr);
+    nassert_raise("duplicate vertex index");
+    return nullptr;
   }
 
   _unique_vertices.insert(vertex);

+ 2 - 1
panda/src/egg/eggXfmAnimData.cxx

@@ -142,7 +142,8 @@ get_value(int row, LMatrix4d &mat) const {
 
     default:
       // The contents string contained an invalid letter.
-      nassertv(false);
+      nassert_raise("invalid letter in contents string");
+      return;
     }
   }
 

+ 2 - 1
panda/src/egg/eggXfmSAnim.cxx

@@ -368,7 +368,8 @@ get_value(int row, LMatrix4d &mat) const {
 
       default:
         // One of the child tables had an invalid name.
-        nassertv(false);
+        nassert_raise("invalid name in child table");
+        return;
       }
     }
   }

+ 1 - 1
panda/src/event/asyncFuture.cxx

@@ -298,7 +298,7 @@ wake_task(AsyncTask *task) {
     return;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected task state");
     return;
   }
 }

+ 2 - 2
panda/src/event/pointerEvent.cxx

@@ -29,7 +29,7 @@ output(std::ostream &out) const {
  */
 void PointerEvent::
 write_datagram(Datagram &dg) const {
-  nassertv(false && "This function not implemented yet.");
+  nassert_raise("This function not implemented yet.");
 }
 
 /**
@@ -37,5 +37,5 @@ write_datagram(Datagram &dg) const {
  */
 void PointerEvent::
 read_datagram(DatagramIterator &scan) {
-  nassertv(false && "This function not implemented yet.");
+  nassert_raise("This function not implemented yet.");
 }

+ 4 - 1
panda/src/express/multifile.cxx

@@ -333,7 +333,10 @@ close() {
   if (_owns_stream) {
     // We prefer to delete the IStreamWrapper over the ostream, if possible.
     if (_read != nullptr) {
-      delete _read;
+      // Only delete it if no SubStream is still referencing it.
+      if (!_read->unref()) {
+        delete _read;
+      }
     } else if (_write != nullptr) {
       delete _write;
     }

+ 1 - 1
panda/src/express/patchfile.cxx

@@ -32,7 +32,7 @@
 #endif  // HAVE_TAR
 
 #ifdef HAVE_TAR
-istream *Patchfile::_tar_istream = nullptr;
+std::istream *Patchfile::_tar_istream = nullptr;
 #endif  // HAVE_TAR
 
 using std::endl;

+ 13 - 0
panda/src/express/subStreamBuf.cxx

@@ -88,6 +88,13 @@ open(IStreamWrapper *source, OStreamWrapper *dest, streampos start, streampos en
   _append = append;
   _gpos = _start;
   _ppos = _start;
+
+  if (source != nullptr) {
+    source->ref();
+  }
+  if (dest != nullptr) {
+    dest->ref();
+  }
 }
 
 /**
@@ -98,6 +105,12 @@ close() {
   // Make sure the write buffer is flushed.
   sync();
 
+  if (_source != nullptr && !_source->unref()) {
+    delete _source;
+  }
+  if (_dest != nullptr && !_dest->unref()) {
+    delete _dest;
+  }
   _source = nullptr;
   _dest = nullptr;
   _start = 0;

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

@@ -23,6 +23,7 @@
 class EXPCL_PANDA_EXPRESS SubStreamBuf : public std::streambuf {
 public:
   SubStreamBuf();
+  SubStreamBuf(const SubStreamBuf &copy) = delete;
   virtual ~SubStreamBuf();
 
   void open(IStreamWrapper *source, OStreamWrapper *dest, std::streampos start, std::streampos end, bool append);

+ 17 - 10
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -210,6 +210,23 @@ FfmpegAudioCursor::
  */
 void FfmpegAudioCursor::
 cleanup() {
+  if (_audio_ctx && _audio_ctx->codec) {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
+    // We need to drain the codec to prevent a memory leak.
+    avcodec_send_packet(_audio_ctx, nullptr);
+    while (avcodec_receive_frame(_audio_ctx, _frame) == 0) {}
+    avcodec_flush_buffers(_audio_ctx);
+#endif
+
+    avcodec_close(_audio_ctx);
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
+    avcodec_free_context(&_audio_ctx);
+#else
+    delete _audio_ctx;
+#endif
+  }
+  _audio_ctx = nullptr;
+
   if (_frame) {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
     av_frame_free(&_frame);
@@ -237,16 +254,6 @@ cleanup() {
     _buffer = nullptr;
   }
 
-  if ((_audio_ctx)&&(_audio_ctx->codec)) {
-    avcodec_close(_audio_ctx);
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
-    avcodec_free_context(&_audio_ctx);
-#else
-    delete _audio_ctx;
-#endif
-  }
-  _audio_ctx = nullptr;
-
   if (_format_ctx) {
     _ffvfile.close();
     _format_ctx = nullptr;

+ 8 - 1
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -595,7 +595,14 @@ close_stream() {
   // Hold the global lock while we free avcodec objects.
   ReMutexHolder av_holder(_av_lock);
 
-  if ((_video_ctx)&&(_video_ctx->codec)) {
+  if (_video_ctx && _video_ctx->codec) {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
+    // We need to drain the codec to prevent a memory leak.
+    avcodec_send_packet(_video_ctx, nullptr);
+    while (avcodec_receive_frame(_video_ctx, _frame) == 0) {}
+    avcodec_flush_buffers(_video_ctx);
+#endif
+
     avcodec_close(_video_ctx);
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
     avcodec_free_context(&_video_ctx);

+ 3 - 0
panda/src/ffmpeg/ffmpegVirtualFile.cxx

@@ -189,7 +189,10 @@ register_protocol() {
   }
 
   // Here's a good place to call this global ffmpeg initialization function.
+  // However, ffmpeg (but not libav) deprecated this, hence this check.
+#if LIBAVFORMAT_VERSION_MICRO < 100 || LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
   av_register_all();
+#endif
 
   // And this one.
   avformat_network_init();

+ 1 - 1
panda/src/glstuff/glGeomContext_src.cxx

@@ -32,7 +32,7 @@ get_display_list(GLuint &index, const CLP(GeomMunger) *munger,
                  UpdateSeq modified) {
 #if defined(OPENGLES) || !defined(SUPPORT_FIXED_FUNCTION)
   // Display lists not supported by OpenGL ES.
-  nassertr(false, false);
+  nassert_raise("OpenGL ES does not support display lists");
   return false;
 
 #else

+ 6 - 2
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -113,8 +113,6 @@ clear(Thread *current_thread) {
       << get_name() << " " << (void *)this << "\n";
   }
 
-  PStatGPUTimer timer(glgsg, glgsg->_clear_pcollector);
-
   // Disable the scissor test, so we can clear the whole buffer.
   glDisable(GL_SCISSOR_TEST);
   glgsg->_scissor_enabled = false;
@@ -232,6 +230,9 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     }
   }
 
+  CLP(GraphicsStateGuardian) *glgsg = (CLP(GraphicsStateGuardian) *)_gsg.p();
+  glgsg->push_group_marker(std::string(CLASSPREFIX_QUOTED "GraphicsBuffer ") + get_name());
+
   // Figure out the desired size of the  buffer.
   if (mode == FM_render) {
     clear_cube_map_selection();
@@ -257,6 +258,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     if (_needs_rebuild) {
       // If we still need rebuild, something went wrong with
       // rebuild_bitplanes().
+      glgsg->pop_group_marker();
       return false;
     }
 
@@ -1316,6 +1318,8 @@ end_frame(FrameMode mode, Thread *current_thread) {
     clear_cube_map_selection();
   }
   report_my_gl_errors();
+
+  glgsg->pop_group_marker();
 }
 
 /**

+ 24 - 0
panda/src/glstuff/glGraphicsStateGuardian_src.I

@@ -11,6 +11,30 @@
  * @date 1999-02-02
  */
 
+/**
+ * If debug markers are enabled, pushes the beginning of a group marker.
+ */
+INLINE void CLP(GraphicsStateGuardian)::
+push_group_marker(const std::string &marker) {
+#if !defined(NDEBUG) && !defined(OPENGLES_1)
+  if (_glPushGroupMarker != nullptr) {
+    _glPushGroupMarker(marker.size(), marker.data());
+  }
+#endif
+}
+
+/**
+ * If debug markers are enabled, closes a group debug marker.
+ */
+INLINE void CLP(GraphicsStateGuardian)::
+pop_group_marker() {
+#if !defined(NDEBUG) && !defined(OPENGLES_1)
+  if (_glPopGroupMarker != nullptr) {
+    _glPopGroupMarker();
+  }
+#endif
+}
+
 /**
  * Checks for any outstanding error codes and outputs them, if found.  If
  * NDEBUG is defined, this function does nothing.  The return value is true if

+ 142 - 13
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -91,6 +91,7 @@ PStatCollector CLP(GraphicsStateGuardian)::_vertex_array_update_pcollector("Draw
 PStatCollector CLP(GraphicsStateGuardian)::_texture_update_pcollector("Draw:Update texture");
 PStatCollector CLP(GraphicsStateGuardian)::_fbo_bind_pcollector("Draw:Bind FBO");
 PStatCollector CLP(GraphicsStateGuardian)::_check_error_pcollector("Draw:Check errors");
+PStatCollector CLP(GraphicsStateGuardian)::_check_residency_pcollector("*:PStats:Check residency");
 
 // The following noop functions are assigned to the corresponding glext
 // function pointers in the class, in case the functions are not defined by
@@ -616,6 +617,22 @@ reset() {
   // Print out a list of all extensions.
   report_extensions();
 
+  // Check if we are running under a profiling tool such as apitrace.
+#if !defined(NDEBUG) && !defined(OPENGLES_1)
+  if (has_extension("GL_EXT_debug_marker")) {
+    _glPushGroupMarker = (PFNGLPUSHGROUPMARKEREXTPROC)
+      get_extension_func("glPushGroupMarkerEXT");
+    _glPopGroupMarker = (PFNGLPOPGROUPMARKEREXTPROC)
+      get_extension_func("glPopGroupMarkerEXT");
+
+    // Start a group right away.
+    push_group_marker("reset");
+  } else {
+    _glPushGroupMarker = nullptr;
+    _glPopGroupMarker = nullptr;
+  }
+#endif
+
   // Initialize OpenGL debugging output first, if enabled and supported.
   _supports_debug = false;
   _use_object_labels = false;
@@ -1737,14 +1754,6 @@ reset() {
        get_extension_func("glUniform3iv");
     _glUniform4iv = (PFNGLUNIFORM4IVPROC)
        get_extension_func("glUniform4iv");
-    _glUniform1uiv = (PFNGLUNIFORM1UIVPROC)
-       get_extension_func("glUniform1uiv");
-    _glUniform2uiv = (PFNGLUNIFORM2UIVPROC)
-       get_extension_func("glUniform2uiv");
-    _glUniform3uiv = (PFNGLUNIFORM3UIVPROC)
-       get_extension_func("glUniform3uiv");
-    _glUniform4uiv = (PFNGLUNIFORM4UIVPROC)
-       get_extension_func("glUniform4uiv");
     _glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)
        get_extension_func("glUniformMatrix3fv");
     _glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)
@@ -1759,9 +1768,35 @@ reset() {
        get_extension_func("glVertexAttribPointer");
 
     if (is_at_least_gl_version(3, 0)) {
+      _glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)
+         get_extension_func("glBindFragDataLocation");
       _glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)
          get_extension_func("glVertexAttribIPointer");
+      _glUniform1uiv = (PFNGLUNIFORM1UIVPROC)
+         get_extension_func("glUniform1uiv");
+      _glUniform2uiv = (PFNGLUNIFORM2UIVPROC)
+         get_extension_func("glUniform2uiv");
+      _glUniform3uiv = (PFNGLUNIFORM3UIVPROC)
+         get_extension_func("glUniform3uiv");
+      _glUniform4uiv = (PFNGLUNIFORM4UIVPROC)
+         get_extension_func("glUniform4uiv");
+
+    } else if (has_extension("GL_EXT_gpu_shader4")) {
+      _glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)
+         get_extension_func("glBindFragDataLocationEXT");
+      _glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)
+         get_extension_func("glVertexAttribIPointerEXT");
+      _glUniform1uiv = (PFNGLUNIFORM1UIVPROC)
+         get_extension_func("glUniform1uivEXT");
+      _glUniform2uiv = (PFNGLUNIFORM2UIVPROC)
+         get_extension_func("glUniform2uivEXT");
+      _glUniform3uiv = (PFNGLUNIFORM3UIVPROC)
+         get_extension_func("glUniform3uivEXT");
+      _glUniform4uiv = (PFNGLUNIFORM4UIVPROC)
+         get_extension_func("glUniform4uivEXT");
+
     } else {
+      _glBindFragDataLocation = nullptr;
       _glVertexAttribIPointer = nullptr;
     }
     if (is_at_least_gl_version(4, 1) ||
@@ -1790,6 +1825,7 @@ reset() {
     _glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)
        get_extension_func("glVertexAttribPointerARB");
 
+    _glBindFragDataLocation = nullptr;
     _glVertexAttribIPointer = nullptr;
     _glVertexAttribLPointer = nullptr;
   }
@@ -1841,6 +1877,13 @@ reset() {
   } else {
     _glVertexAttribIPointer = nullptr;
   }
+
+  if (has_extension("GL_EXT_blend_func_extended")) {
+    _glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)
+       get_extension_func("glBindFragDataLocationEXT");
+  } else {
+    _glBindFragDataLocation = nullptr;
+  }
 #endif
 
 #ifndef OPENGLES_1
@@ -3373,6 +3416,8 @@ reset() {
   }
 #endif
 
+  pop_group_marker();
+
   // Now that the GSG has been initialized, make it available for
   // optimizations.
   add_gsg(this);
@@ -3405,7 +3450,6 @@ finish() {
  */
 void CLP(GraphicsStateGuardian)::
 clear(DrawableRegion *clearable) {
-  PStatGPUTimer timer(this, _clear_pcollector);
   report_my_gl_errors();
 
   if (!clearable->is_any_clear_active()) {
@@ -3710,6 +3754,10 @@ clear_before_callback() {
   _glClientActiveTexture(GL_TEXTURE0);
 #endif
 
+  // It's also quite reasonable to presume there aren't any funny color write
+  // mask settings active.
+  clear_color_write_mask();
+
   // Clear the bound sampler object, so that we do not inadvertently override
   // the callback's desired sampler settings.
 #ifndef OPENGLES_1
@@ -3932,6 +3980,7 @@ end_frame(Thread *current_thread) {
   // connects PStats, at which point it will then correct the assessment.  No
   // harm done.
   if (has_fixed_function_pipeline() && PStatClient::is_connected()) {
+    PStatTimer timer(_check_residency_pcollector);
     check_nonresident_texture(_prepared_objects->_texture_residency.get_inactive_resident());
     check_nonresident_texture(_prepared_objects->_texture_residency.get_active_resident());
 
@@ -7191,6 +7240,8 @@ do_issue_shade_model() {
  */
 void CLP(GraphicsStateGuardian)::
 do_issue_shader() {
+  PStatTimer timer(_draw_set_state_shader_pcollector);
+
   ShaderContext *context = 0;
   Shader *shader = (Shader *)_target_shader->get_shader();
 
@@ -10640,6 +10691,85 @@ reissue_transforms() {
   _current_vertex_format.clear();
   memset(_vertex_attrib_columns, 0, sizeof(const GeomVertexColumn *) * 32);
 #endif
+
+  // Some libraries (Kivy) leave their buffers bound.  How clumsy of them.
+  if (_supports_buffers) {
+    _glBindBuffer(GL_ARRAY_BUFFER, 0);
+    _glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    _current_vbuffer_index = 0;
+    _current_ibuffer_index = 0;
+  }
+#ifndef OPENGLES
+  if (_supports_glsl) {
+    _glDisableVertexAttribArray(0);
+    _glDisableVertexAttribArray(1);
+  }
+#endif
+
+  // Since this is called by clear_state_and_transform(), we also should reset
+  // the states that won't automatically be respecified when clearing the
+  // state mask.
+  _active_color_write_mask = ColorWriteAttrib::C_all;
+  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+  if (_dithering_enabled) {
+    glEnable(GL_DITHER);
+  } else {
+    glDisable(GL_DITHER);
+  }
+  if (_depth_test_enabled) {
+    glEnable(GL_DEPTH_TEST);
+  } else {
+    glDisable(GL_DEPTH_TEST);
+  }
+  if (_stencil_test_enabled) {
+    glEnable(GL_STENCIL_TEST);
+  } else {
+    glDisable(GL_STENCIL_TEST);
+  }
+  if (_blend_enabled) {
+    glEnable(GL_BLEND);
+  } else {
+    glDisable(GL_BLEND);
+  }
+
+#ifndef OPENGLES_2
+  if (_multisample_mode != 0) {
+    glEnable(GL_MULTISAMPLE);
+  } else {
+    glDisable(GL_MULTISAMPLE);
+    glDisable(GL_SAMPLE_ALPHA_TO_ONE);
+    glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+  }
+  if (_line_smooth_enabled) {
+    glEnable(GL_LINE_SMOOTH);
+  } else {
+    glDisable(GL_LINE_SMOOTH);
+  }
+#endif
+
+#ifndef OPENGLES
+  if (_polygon_smooth_enabled) {
+    glEnable(GL_POLYGON_SMOOTH);
+  } else {
+    glDisable(GL_POLYGON_SMOOTH);
+  }
+#endif
+
+#ifdef SUPPORT_FIXED_FUNCTION
+  if (has_fixed_function_pipeline()) {
+    if (_alpha_test_enabled) {
+      glEnable(GL_ALPHA_TEST);
+    } else {
+      glDisable(GL_ALPHA_TEST);
+    }
+    if (_point_smooth_enabled) {
+      glEnable(GL_POINT_SMOOTH);
+    } else {
+      glDisable(GL_POINT_SMOOTH);
+    }
+  }
+#endif
 }
 
 #ifdef SUPPORT_FIXED_FUNCTION
@@ -10902,7 +11032,6 @@ set_state_and_transform(const RenderState *target,
   _instance_count = _target_shader->get_instance_count();
 
   if (_target_shader != _state_shader) {
-    // PStatGPUTimer timer(this, _draw_set_state_shader_pcollector);
     do_issue_shader();
     _state_shader = _target_shader;
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
@@ -11058,7 +11187,7 @@ set_state_and_transform(const RenderState *target,
   int texture_slot = TextureAttrib::get_class_slot();
   if (_target_rs->get_attrib(texture_slot) != _state_rs->get_attrib(texture_slot) ||
       !_state_mask.get_bit(texture_slot)) {
-    // PStatGPUTimer timer(this, _draw_set_state_texture_pcollector);
+    PStatGPUTimer timer(this, _draw_set_state_texture_pcollector);
     determine_target_texture();
     do_issue_texture();
 
@@ -14015,8 +14144,8 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
                       Texture::ComponentType type,
                       Texture::CompressionMode compression, int n) {
 #ifdef OPENGLES  // Extracting texture data unsupported in OpenGL ES.
-    nassertr(false, false);
-    return false;
+  nassert_raise("OpenGL ES does not support extracting texture data");
+  return false;
 #else
 
   // Make sure the GL driver does not align textures, otherwise we get corrupt

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

@@ -146,6 +146,7 @@ typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum d
 // GLSL shader functions
 typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
 typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name);
+typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name);
 typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
 typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
 typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
@@ -275,6 +276,9 @@ public:
 
   static void APIENTRY debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, GLvoid *userParam);
 
+  INLINE virtual void push_group_marker(const std::string &marker) final;
+  INLINE virtual void pop_group_marker() final;
+
   virtual void reset();
 
   virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
@@ -960,6 +964,7 @@ public:
   // GLSL functions
   PFNGLATTACHSHADERPROC _glAttachShader;
   PFNGLBINDATTRIBLOCATIONPROC _glBindAttribLocation;
+  PFNGLBINDFRAGDATALOCATIONPROC _glBindFragDataLocation;
   PFNGLCOMPILESHADERPROC _glCompileShader;
   PFNGLCREATEPROGRAMPROC _glCreateProgram;
   PFNGLCREATESHADERPROC _glCreateShader;
@@ -1091,6 +1096,11 @@ public:
   GLuint _white_texture;
 
 #ifndef NDEBUG
+#ifndef OPENGLES_1
+  PFNGLPUSHGROUPMARKEREXTPROC _glPushGroupMarker;
+  PFNGLPOPGROUPMARKEREXTPROC _glPopGroupMarker;
+#endif
+
   bool _show_texture_usage;
   int _show_texture_usage_max_size;
   int _show_texture_usage_index;
@@ -1118,6 +1128,7 @@ public:
   static PStatCollector _texture_update_pcollector;
   static PStatCollector _fbo_bind_pcollector;
   static PStatCollector _check_error_pcollector;
+  static PStatCollector _check_residency_pcollector;
 
 public:
   virtual TypeHandle get_type() const {

+ 5 - 0
panda/src/glstuff/glShaderContext_src.cxx

@@ -3219,6 +3219,11 @@ glsl_compile_and_link() {
     _glgsg->_glBindAttribLocation(_glsl_program, 8, "texcoord");
   }
 
+  // Also bind the p3d_FragData array to the first index always.
+  if (_glgsg->_glBindFragDataLocation != nullptr) {
+    _glgsg->_glBindFragDataLocation(_glsl_program, 0, "p3d_FragData");
+  }
+
   // If we requested to retrieve the shader, we should indicate that before
   // linking.
   bool retrieve_binary = false;

+ 7 - 1
panda/src/glxdisplay/glxGraphicsBuffer.cxx

@@ -71,7 +71,10 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
   glxGraphicsStateGuardian *glxgsg;
   DCAST_INTO_R(glxgsg, _gsg, false);
-  glXMakeCurrent(_display, _pbuffer, glxgsg->_context);
+  {
+    LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+    glXMakeCurrent(_display, _pbuffer, glxgsg->_context);
+  }
 
   // Now that we have made the context current to a window, we can reset the
   // GSG state if this is the first time it has been used.  (We can't just
@@ -125,6 +128,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
 void glxGraphicsBuffer::
 close_buffer() {
   if (_gsg != nullptr) {
+    LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
     glXMakeCurrent(_display, None, nullptr);
 
     if (_pbuffer != None) {
@@ -179,6 +183,8 @@ open_buffer() {
 
   nassertr(glxgsg->_supports_pbuffer, false);
 
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+
   static const int max_attrib_list = 32;
   int attrib_list[max_attrib_list];
   int n = 0;

+ 6 - 1
panda/src/glxdisplay/glxGraphicsPixmap.cxx

@@ -74,7 +74,10 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
   glxGraphicsStateGuardian *glxgsg;
   DCAST_INTO_R(glxgsg, _gsg, false);
-  glXMakeCurrent(_display, _glx_pixmap, glxgsg->_context);
+  {
+    LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+    glXMakeCurrent(_display, _glx_pixmap, glxgsg->_context);
+  }
 
   // Now that we have made the context current to a window, we can reset the
   // GSG state if this is the first time it has been used.  (We can't just
@@ -127,6 +130,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
  */
 void glxGraphicsPixmap::
 close_buffer() {
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   if (_gsg != nullptr) {
     glXMakeCurrent(_display, None, nullptr);
     _gsg.clear();
@@ -197,6 +201,7 @@ open_buffer() {
     }
   }
 
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   _x_pixmap = XCreatePixmap(_display, _drawable,
                             get_x_size(), get_y_size(), visual_info->depth);
   if (_x_pixmap == None) {

+ 8 - 0
panda/src/glxdisplay/glxGraphicsStateGuardian.cxx

@@ -64,6 +64,9 @@ glxGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
  */
 glxGraphicsStateGuardian::
 ~glxGraphicsStateGuardian() {
+  // Actually, the lock might have already destructed, so we can't reliably
+  // grab the X11 lock here.
+  //LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   destroy_temp_xwindow();
   if (_visuals != nullptr) {
     XFree(_visuals);
@@ -224,6 +227,7 @@ choose_pixel_format(const FrameBufferProperties &properties,
                     X11_Display *display,
                     int screen, bool need_pbuffer, bool need_pixmap) {
 
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   _display = display;
   _screen = screen;
   _context = nullptr;
@@ -457,6 +461,7 @@ gl_get_error() const {
  */
 void glxGraphicsStateGuardian::
 query_gl_version() {
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   PosixGraphicsStateGuardian::query_gl_version();
 
   show_glx_client_string("GLX_VENDOR", GLX_VENDOR);
@@ -483,6 +488,7 @@ query_gl_version() {
  */
 void glxGraphicsStateGuardian::
 get_extra_extensions() {
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
   save_extensions(glXQueryExtensionsString(_display, _screen));
 }
 
@@ -497,6 +503,8 @@ do_get_extension_func(const char *name) {
   nassertr(name != nullptr, nullptr);
 
   if (glx_get_proc_address) {
+    LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+
     // First, check if we have glXGetProcAddress available.  This will be
     // superior if we can get it.
 

+ 33 - 0
panda/src/glxdisplay/glxGraphicsWindow.cxx

@@ -89,6 +89,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   glxgsg->reset_if_new();
 
   if (mode == FM_render) {
+    glxgsg->push_group_marker(std::string("glxGraphicsWindow ") + get_name());
     // begin_render_texture();
     clear_cube_map_selection();
   }
@@ -97,6 +98,34 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   return _gsg->begin_frame(current_thread);
 }
 
+
+/**
+ * This function will be called within the draw thread after rendering is
+ * completed for a given frame.  It should do whatever finalization is
+ * required.
+ */
+void glxGraphicsWindow::
+end_frame(FrameMode mode, Thread *current_thread) {
+  end_frame_spam(mode);
+  nassertv(_gsg != nullptr);
+
+  if (mode == FM_render) {
+    // end_render_texture();
+    copy_to_textures();
+  }
+
+  _gsg->end_frame(current_thread);
+
+  if (mode == FM_render) {
+    trigger_flip();
+    clear_cube_map_selection();
+
+    glxGraphicsStateGuardian *glxgsg;
+    DCAST_INTO_V(glxgsg, _gsg);
+    glxgsg->pop_group_marker();
+  }
+}
+
 /**
  * This function will be called within the draw thread after begin_flip() has
  * been called on all windows, to finish the exchange of the front and back
@@ -125,6 +154,8 @@ end_flip() {
  */
 void glxGraphicsWindow::
 close_window() {
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+
   if (_gsg != nullptr) {
     glXMakeCurrent(_display, None, nullptr);
     _gsg.clear();
@@ -175,6 +206,8 @@ open_window() {
     return false;
   }
 
+  LightReMutexHolder holder(glxGraphicsPipe::_x_mutex);
+
   if (glxgsg->_fbconfig != None) {
     setup_colormap(glxgsg->_fbconfig);
   } else {

+ 1 - 0
panda/src/glxdisplay/glxGraphicsWindow.h

@@ -36,6 +36,7 @@ public:
   virtual ~glxGraphicsWindow() {};
 
   virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
   virtual void end_flip();
 
 protected:

+ 6 - 3
panda/src/gobj/geom.cxx

@@ -1504,7 +1504,7 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
   } else {
     // If this assertion fails, clear_prepared() was given a prepared_objects
     // that the geom didn't know about.
-    nassertv(false);
+    nassert_raise("unknown PreparedGraphicsObjects");
   }
 }
 
@@ -1844,8 +1844,11 @@ check_valid(const GeomVertexDataPipelineReader *data_reader) const {
 bool GeomPipelineReader::
 draw(GraphicsStateGuardianBase *gsg,
      const GeomVertexDataPipelineReader *data_reader, bool force) const {
-  PStatTimer timer(Geom::_draw_primitive_setup_pcollector);
-  bool all_ok = gsg->begin_draw_primitives(this, data_reader, force);
+  bool all_ok;
+  {
+    PStatTimer timer(Geom::_draw_primitive_setup_pcollector);
+    all_ok = gsg->begin_draw_primitives(this, data_reader, force);
+  }
   if (all_ok) {
     Geom::Primitives::const_iterator pi;
     for (pi = _cdata->_primitives.begin();

+ 1 - 1
panda/src/gobj/geomCacheManager.cxx

@@ -47,7 +47,7 @@ GeomCacheManager() :
 GeomCacheManager::
 ~GeomCacheManager() {
   // Shouldn't be deleting this global object.
-  nassertv(false);
+  nassert_raise("attempt to delete GeomCacheManager");
 }
 
 /**

+ 4 - 4
panda/src/gobj/geomPrimitive.cxx

@@ -221,7 +221,7 @@ add_vertex(int vertex) {
       ((uint32_t *)ptr)[num_rows] = vertex;
       break;
     default:
-      nassertv(false);
+      nassert_raise("unsupported index type");
       break;
     }
   }
@@ -1510,7 +1510,7 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
   } else {
     // If this assertion fails, clear_prepared() was given a prepared_objects
     // which the data array didn't know about.
-    nassertv(false);
+    nassert_raise("unknown PreparedGraphicsObjects");
   }
 }
 
@@ -2230,7 +2230,7 @@ get_vertex(int i) const {
       return ((uint32_t *)ptr)[i];
       break;
     default:
-      nassertr(false, -1);
+      nassert_raise("unsupported index type");
       return -1;
     }
 
@@ -2296,7 +2296,7 @@ get_referenced_vertices(BitArray &bits) const {
       }
       break;
     default:
-      nassertv(false);
+      nassert_raise("unsupported index type");
       break;
     }
   } else {

+ 1 - 1
panda/src/gobj/geomTrifans.cxx

@@ -139,7 +139,7 @@ CPT(GeomVertexArrayData) GeomTrifans::
 rotate_impl() const {
   // Actually, we can't rotate fans without chaging the winding order.  It's
   // an error to define a flat shade model for a GeomTrifan.
-  nassertr(false, nullptr);
+  nassert_raise("GeomTrifans cannot have flat shading model");
   return nullptr;
 }
 

+ 1 - 1
panda/src/gobj/geomVertexArrayData.cxx

@@ -351,7 +351,7 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
   } else {
     // If this assertion fails, clear_prepared() was given a prepared_objects
     // which the data array didn't know about.
-    nassertv(false);
+    nassert_raise("unknown PreparedGraphicsObjects");
   }
 }
 

+ 2 - 1
panda/src/gobj/geomVertexData.cxx

@@ -1121,7 +1121,8 @@ do_set_color(GeomVertexData *vdata, const LColor &color) {
   const GeomVertexColumn *column;
   int array_index;
   if (!format->get_array_info(InternalName::get_color(), array_index, column)) {
-    nassertv(false);
+    nassert_raise("no color column");
+    return;
   }
 
   size_t stride = format->get_array(array_index)->get_stride();

+ 1 - 1
panda/src/gobj/shader.cxx

@@ -3705,7 +3705,7 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
   } else {
     // If this assertion fails, clear_prepared() was given a prepared_objects
     // which the texture didn't know about.
-    nassertv(false);
+    nassert_raise("unknown PreparedGraphicsObjects");
   }
 }
 

+ 8 - 5
panda/src/gobj/texture.cxx

@@ -4897,8 +4897,8 @@ do_read_ktx(CData *cdata, istream &in, const string &filename, bool header_only)
             }
             break;
           default:
-            nassertr(false, false);
-            break;
+            nassert_raise("unexpected channel count");
+            return false;
           }
         }
 
@@ -6591,8 +6591,9 @@ do_reconsider_image_properties(CData *cdata, int x_size, int y_size, int num_com
 
     default:
       // Eh?
-      nassertr(false, false);
+      nassert_raise("unexpected channel count");
       cdata->_format = F_rgb;
+      return false;
     }
   }
 
@@ -7989,7 +7990,8 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    return;
   }
 
   nassertv((unsigned char *)p == &image[idx] + page_size);
@@ -8199,7 +8201,8 @@ convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
     break;
 
   default:
-    nassertr(false, false);
+    nassert_raise("unexpected channel count");
+    return false;
   }
 
   nassertr((unsigned char *)p == &image[idx] + page_size, false);

+ 36 - 6
panda/src/grutil/sceneGraphAnalyzerMeter.cxx

@@ -19,6 +19,7 @@
 #include "depthTestAttrib.h"
 #include "depthWriteAttrib.h"
 #include "pStatTimer.h"
+#include "omniBoundingVolume.h"
 #include <stdio.h>  // For sprintf/snprintf
 
 PStatCollector SceneGraphAnalyzerMeter::_show_analyzer_pcollector("*:Show scene graph analysis");
@@ -29,9 +30,16 @@ TypeHandle SceneGraphAnalyzerMeter::_type_handle;
  *
  */
 SceneGraphAnalyzerMeter::
-SceneGraphAnalyzerMeter(const std::string &name, PandaNode *node) : TextNode(name) {
+SceneGraphAnalyzerMeter(const std::string &name, PandaNode *node) :
+  TextNode(name),
+  _last_aspect_ratio(-1) {
+
   set_cull_callback();
 
+  // Don't do frustum culling, as the text will always be in view.
+  set_bounds(new OmniBoundingVolume());
+  set_final(true);
+
   Thread *current_thread = Thread::get_current_thread();
 
   _update_interval = scene_graph_analyzer_meter_update_interval;
@@ -41,7 +49,7 @@ SceneGraphAnalyzerMeter(const std::string &name, PandaNode *node) : TextNode(nam
 
   set_align(A_left);
   set_transform(LMatrix4::scale_mat(scene_graph_analyzer_meter_scale) *
-                LMatrix4::translate_mat(LVector3::rfu(-1.0f + scene_graph_analyzer_meter_side_margins * scene_graph_analyzer_meter_scale, 0.0f, 1.0f - scene_graph_analyzer_meter_scale)));
+                LMatrix4::translate_mat(LVector3::rfu(scene_graph_analyzer_meter_side_margins * scene_graph_analyzer_meter_scale, 0.0f, -scene_graph_analyzer_meter_scale)));
   set_card_color(0.0f, 0.0f, 0.0f, 0.4);
   set_card_as_margin(scene_graph_analyzer_meter_side_margins, scene_graph_analyzer_meter_side_margins, 0.1f, 0.0f);
   set_usage_hint(Geom::UH_client);
@@ -77,6 +85,11 @@ setup_window(GraphicsOutput *window) {
   _root.set_material_off(1);
   _root.set_two_sided(1, 1);
 
+  // If we don't set this explicitly, Panda will cause it to be rendered
+  // in a back-to-front cull bin, which will cause the bounding volume
+  // to be computed unnecessarily.  Saves a little bit of overhead.
+  _root.set_bin("unsorted", 0);
+
   // Create a display region that covers the entire window.
   _display_region = _window->make_display_region();
   _display_region->set_sort(scene_graph_analyzer_meter_layer_sort);
@@ -87,10 +100,11 @@ setup_window(GraphicsOutput *window) {
 
   PT(Lens) lens = new OrthographicLens;
 
-  static const PN_stdfloat left = -1.0f;
-  static const PN_stdfloat right = 1.0f;
-  static const PN_stdfloat bottom = -1.0f;
-  static const PN_stdfloat top = 1.0f;
+  // We choose these values such that we can place the text against (0, 0).
+  static const PN_stdfloat left = 0.0f;
+  static const PN_stdfloat right = 2.0f;
+  static const PN_stdfloat bottom = -2.0f;
+  static const PN_stdfloat top = 0.0f;
   lens->set_film_size(right - left, top - bottom);
   lens->set_film_offset((right + left) * 0.5, (top + bottom) * 0.5);
   lens->set_near_far(-1000, 1000);
@@ -138,6 +152,22 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   // Statistics
   PStatTimer timer(_show_analyzer_pcollector, current_thread);
 
+  // This is probably a good time to check if the aspect ratio on the window
+  // has changed.
+  int width = _display_region->get_pixel_width();
+  int height = _display_region->get_pixel_height();
+  PN_stdfloat aspect_ratio = 1;
+  if (width != 0 && height != 0) {
+    aspect_ratio = (PN_stdfloat)height / (PN_stdfloat)width;
+  }
+
+  // Scale the transform by the calculated aspect ratio.
+  if (aspect_ratio != _last_aspect_ratio) {
+    _aspect_ratio_transform = TransformState::make_scale(LVecBase3(aspect_ratio, 1, 1));
+    _last_aspect_ratio = aspect_ratio;
+  }
+  data._net_transform = data._net_transform->compose(_aspect_ratio_transform);
+
   // Check to see if it's time to update.
   double now = _clock_object->get_frame_time(current_thread);
   double elapsed = now - _last_update;

+ 3 - 0
panda/src/grutil/sceneGraphAnalyzerMeter.h

@@ -72,6 +72,9 @@ private:
   PandaNode *_node;
   ClockObject *_clock_object;
 
+  PN_stdfloat _last_aspect_ratio;
+  CPT(TransformState) _aspect_ratio_transform;
+
   static PStatCollector _show_analyzer_pcollector;
 
 public:

+ 1 - 1
panda/src/gsgbase/graphicsStateGuardianBase.cxx

@@ -54,7 +54,7 @@ set_default_gsg(GraphicsStateGuardianBase *default_gsg) {
   LightMutexHolder holder(gsg_list->_lock);
   if (find(gsg_list->_gsgs.begin(), gsg_list->_gsgs.end(), default_gsg) == gsg_list->_gsgs.end()) {
     // The specified GSG doesn't exist or it has already destructed.
-    nassertv(false);
+    nassert_raise("GSG not found or already destructed");
     return;
   }
 

+ 3 - 0
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -235,6 +235,9 @@ public:
 #endif
   }
 
+  virtual void push_group_marker(const std::string &marker) {}
+  virtual void pop_group_marker() {}
+
 PUBLISHED:
   static GraphicsStateGuardianBase *get_default_gsg();
   static void set_default_gsg(GraphicsStateGuardianBase *default_gsg);

+ 5 - 1
panda/src/movies/flacAudioCursor.cxx

@@ -59,7 +59,8 @@ FlacAudioCursor::
 FlacAudioCursor(FlacAudio *src, std::istream *stream) :
   MovieAudioCursor(src),
   _is_valid(false),
-  _drflac(nullptr)
+  _drflac(nullptr),
+  _stream(stream)
 {
   nassertv(stream != nullptr);
   nassertv(stream->good());
@@ -91,6 +92,9 @@ FlacAudioCursor::
   if (_drflac != nullptr) {
     drflac_close(_drflac);
   }
+  if (_stream != nullptr) {
+    VirtualFileSystem::close_read_file(_stream);
+  }
 }
 
 /**

+ 1 - 0
panda/src/movies/flacAudioCursor.h

@@ -41,6 +41,7 @@ public:
 
 protected:
   drflac *_drflac;
+  std::istream *_stream;
 
 public:
   static TypeHandle get_class_type() {

+ 3 - 3
panda/src/nativenet/socket_address.I

@@ -43,7 +43,7 @@ Socket_Address(const struct sockaddr &inaddr) {
     _addr6 = (const sockaddr_in6 &)inaddr;
 
   } else {
-    nassertv(false);
+    nassert_raise("unsupported address family");
     clear();
   }
 }
@@ -106,7 +106,7 @@ operator == (const Socket_Address &in) const {
   }
 
   // Unsupported address family.
-  nassertr(false, false);
+  nassert_raise("unsupported address family");
   return false;
 }
 
@@ -224,7 +224,7 @@ operator < (const Socket_Address &in) const {
   }
 
   // Unsupported address family.
-  nassertr(false, false);
+  nassert_raise("unsupported address family");
   return false;
 }
 

+ 2 - 2
panda/src/nativenet/socket_address.cxx

@@ -98,7 +98,7 @@ get_ip() const {
     getnameinfo(&_addr, sizeof(sockaddr_in6), buf, sizeof(buf), nullptr, 0, NI_NUMERICHOST);
 
   } else {
-    nassertr(false, std::string());
+    nassert_raise("unsupported address family");
   }
 
   return std::string(buf);
@@ -124,7 +124,7 @@ get_ip_port() const {
     sprintf(buf + strlen(buf), "]:%hu", get_port());
 
   } else {
-    nassertr(false, std::string());
+    nassert_raise("unsupported address family");
   }
 
   return std::string(buf);

+ 2 - 1
panda/src/net/connection.cxx

@@ -478,7 +478,8 @@ check_send_error(bool okflag) {
   if (!okflag) {
     static ConfigVariableBool abort_send_error("abort-send-error", false);
     if (abort_send_error) {
-      nassertr(false, false);
+      nassert_raise("send error");
+      return false;
     }
 
     // Assume any error means the connection has been reset; tell our manager

+ 2 - 1
panda/src/net/datagramTCPHeader.cxx

@@ -46,7 +46,8 @@ DatagramTCPHeader(const NetDatagram &datagram, int header_size) {
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("invalid header size");
+    return;
   }
 
   nassertv((int)_header.get_length() == header_size);

+ 1 - 1
panda/src/parametrics/parametricCurveCollection.cxx

@@ -276,7 +276,7 @@ get_timewarp_curve(int n) const {
       n--;
     }
   }
-  nassertr(false, nullptr);
+  nassert_raise("index out of range");
   return nullptr;
 }
 

+ 4 - 4
panda/src/pgraph/clipPlaneAttrib.cxx

@@ -72,7 +72,7 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane) {
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -110,7 +110,7 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2) {
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -152,7 +152,7 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -197,7 +197,7 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 

+ 1 - 1
panda/src/pgraph/cullBinManager.cxx

@@ -199,7 +199,7 @@ make_new_bin(int bin_index, GraphicsStateGuardianBase *gsg,
   }
 
   // Hmm, unknown (or unregistered) bin type.
-  nassertr(false, nullptr);
+  nassert_raise("unknown bin type");
   return nullptr;
 }
 

+ 3 - 0
panda/src/pgraph/cullResult.cxx

@@ -295,7 +295,10 @@ draw(Thread *current_thread) {
     nassertv(bin_index >= 0);
 
     if (bin_index < (int)_bins.size() && _bins[bin_index] != nullptr) {
+
+      _gsg->push_group_marker(_bins[bin_index]->get_name());
       _bins[bin_index]->draw(force, current_thread);
+      _gsg->pop_group_marker();
     }
   }
 }

+ 4 - 4
panda/src/pgraph/lightAttrib.cxx

@@ -116,7 +116,7 @@ make(LightAttrib::Operation op, Light *light) {
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -154,7 +154,7 @@ make(LightAttrib::Operation op, Light *light1, Light *light2) {
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -196,7 +196,7 @@ make(LightAttrib::Operation op, Light *light1, Light *light2,
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 
@@ -241,7 +241,7 @@ make(LightAttrib::Operation op, Light *light1, Light *light2,
     return attrib;
   }
 
-  nassertr(false, make());
+  nassert_raise("invalid operation");
   return make();
 }
 

+ 6 - 3
panda/src/pgraph/nodePath.cxx

@@ -718,7 +718,8 @@ get_state(const NodePath &other, Thread *current_thread) const {
     } else {
       pgraph_cat.error()
         << *this << " is not related to " << other << "\n";
-      nassertr(false, RenderState::make_empty());
+      nassert_raise("unrelated nodes");
+      return RenderState::make_empty();
     }
   }
 
@@ -792,7 +793,8 @@ get_transform(const NodePath &other, Thread *current_thread) const {
     } else {
       pgraph_cat.error()
         << *this << " is not related to " << other << "\n";
-      nassertr(false, TransformState::make_identity());
+      nassert_raise("unrelated nodes");
+      return TransformState::make_identity();
     }
   }
 
@@ -877,7 +879,8 @@ get_prev_transform(const NodePath &other, Thread *current_thread) const {
     } else {
       pgraph_cat.error()
         << *this << " is not related to " << other << "\n";
-      nassertr(false, TransformState::make_identity());
+      nassert_raise("unrelated nodes");
+      return TransformState::make_identity();
     }
   }
 

+ 2 - 1
panda/src/pgraph/pandaNode.cxx

@@ -2383,7 +2383,8 @@ r_copy_subgraph(PandaNode::InstanceMap &inst_map, Thread *current_thread) const
       << "Don't know how to copy nodes of type " << get_type() << "\n";
 
     if (no_unsupported_copy) {
-      nassertr(false, nullptr);
+      nassert_raise("unsupported copy");
+      return nullptr;
     }
   }
 

+ 1 - 1
panda/src/pgraph/renderAttribRegistry.cxx

@@ -78,7 +78,7 @@ register_slot(TypeHandle type_handle, int sort, RenderAttrib *default_attrib) {
     pgraph_cat->error()
       << "Too many registered RenderAttribs; not registering "
       << type_handle << "\n";
-    nassertr(false, 0);
+    nassert_raise("out of RenderAttrib slots");
     return 0;
   }
 

+ 2 - 1
panda/src/pgraph/sceneGraphReducer.cxx

@@ -333,7 +333,8 @@ r_apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs,
             << child_node->get_type() << "\n";
 
           if (no_unsupported_copy) {
-            nassertv(false);
+            nassert_raise("unsupported copy");
+            return;
           }
           resist_copy = true;
 

+ 1 - 1
panda/src/pgraphnodes/ambientLight.cxx

@@ -85,7 +85,7 @@ void AmbientLight::
 bind(GraphicsStateGuardianBase *, const NodePath &, int) {
   // AmbientLights aren't bound to light id's; this function should never be
   // called.
-  nassertv(false);
+  nassert_raise("cannot bind AmbientLight");
 }
 
 /**

+ 2 - 1
panda/src/pipeline/pythonThread.cxx

@@ -161,7 +161,8 @@ call_python_func(PyObject *function, PyObject *args) {
 #ifndef HAVE_THREADS
     // Shouldn't be possible to come here without having some kind of
     // threading support enabled.
-    nassertr(false, nullptr);
+    nassert_raise("threading support disabled");
+    return nullptr;
 #else
 
 #ifdef SIMPLE_THREADS

+ 16 - 8
panda/src/pnmimage/pfmFile.cxx

@@ -346,7 +346,8 @@ load(const PNMImage &pnmimage) {
     break;
 
   default:
-    nassertr(false, false);
+    nassert_raise("unexpected channel count");
+    return false;
   }
   return true;
 }
@@ -410,7 +411,8 @@ store(PNMImage &pnmimage) const {
     break;
 
   default:
-    nassertr(false, false);
+    nassert_raise("unexpected channel count");
+    return false;
   }
   return true;
 }
@@ -877,7 +879,8 @@ set_no_data_nan(int num_channels) {
       _has_point = has_point_nan_4;
       break;
     default:
-      nassertv(false);
+      nassert_raise("unexpected channel count");
+      break;
     }
   } else {
     clear_no_data_value();
@@ -909,7 +912,8 @@ set_no_data_value(const LPoint4f &no_data_value) {
     _has_point = has_point_4;
     break;
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    break;
   }
 }
 
@@ -938,7 +942,8 @@ set_no_data_threshold(const LPoint4f &no_data_value) {
     _has_point = has_point_threshold_4;
     break;
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    break;
   }
 }
 
@@ -1121,7 +1126,8 @@ quick_filter_from(const PfmFile &from) {
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    return;
   }
 
   new_data.push_back(0.0);
@@ -1826,7 +1832,8 @@ compute_planar_bounds(const LPoint2f &center, PN_float32 point_dist, PN_float32
     break;
 
   default:
-    nassertr(false, nullptr);
+    nassert_raise("invalid coordinate system");
+    return nullptr;
   }
 
   // Rotate the bounding volume back into the original space of the screen.
@@ -1879,7 +1886,8 @@ compute_sample_point(LPoint3f &result,
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    break;
   }
 }
 

+ 10 - 8
panda/src/pnmimage/pnmImage.cxx

@@ -596,8 +596,8 @@ set_color_space(ColorSpace color_space) {
       break;
 
     default:
-      nassertv(false);
-      break;
+      nassert_raise("invalid color space");
+      return;
     }
 
     // Initialize the new encoding settings.
@@ -852,7 +852,7 @@ get_channel_val(int x, int y, int channel) const {
     pnmimage_cat.error()
       << "Invalid request for channel " << channel << " in "
       << get_num_channels() << "-channel image.\n";
-    nassertr(false, 0);
+    nassert_raise("unexpected channel count");
     return 0;
   }
 }
@@ -888,7 +888,8 @@ set_channel_val(int x, int y, int channel, xelval value) {
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    break;
   }
 }
 
@@ -918,7 +919,7 @@ get_channel(int x, int y, int channel) const {
     pnmimage_cat.error()
       << "Invalid request for channel " << channel << " in "
       << get_num_channels() << "-channel image.\n";
-    nassertr(false, 0);
+    nassert_raise("unexpected channel count");
     return 0;
   }
 }
@@ -954,7 +955,8 @@ set_channel(int x, int y, int channel, float value) {
     break;
 
   default:
-    nassertv(false);
+    nassert_raise("unexpected channel count");
+    break;
   }
 }
 
@@ -2126,7 +2128,7 @@ setup_encoding() {
       break;
 
     default:
-      nassertv(false);
+      nassert_raise("invalid color space");
       break;
     }
   } else {
@@ -2153,7 +2155,7 @@ setup_encoding() {
       break;
 
     default:
-      nassertv(false);
+      nassert_raise("invalid color space");
       break;
     }
   }

+ 0 - 23
panda/src/pnmimage/pnmImageHeader.I

@@ -295,29 +295,6 @@ PixelSpec(const xel &rgb, xelval alpha) :
 {
 }
 
-/**
- *
- */
-INLINE PNMImageHeader::PixelSpec::
-PixelSpec(const PixelSpec &copy) :
-  _red(copy._red),
-  _green(copy._green),
-  _blue(copy._blue),
-  _alpha(copy._alpha)
-{
-}
-
-/**
- *
- */
-INLINE void PNMImageHeader::PixelSpec::
-operator = (const PixelSpec &copy) {
-  _red = copy._red;
-  _green = copy._green;
-  _blue = copy._blue;
-  _alpha = copy._alpha;
-}
-
 /**
  *
  */

+ 3 - 2
panda/src/pnmimage/pnmImageHeader.h

@@ -113,6 +113,9 @@ PUBLISHED:
   // make_histogram().  Note that pixels are stored by integer value, not by
   // floating-point scaled value.
   class EXPCL_PANDA_PNMIMAGE PixelSpec {
+  public:
+    INLINE PixelSpec() = default;
+
   PUBLISHED:
     INLINE PixelSpec(xelval gray_value);
     INLINE PixelSpec(xelval gray_value, xelval alpha);
@@ -120,8 +123,6 @@ PUBLISHED:
     INLINE PixelSpec(xelval red, xelval green, xelval blue, xelval alpha);
     INLINE PixelSpec(const xel &rgb);
     INLINE PixelSpec(const xel &rgb, xelval alpha);
-    INLINE PixelSpec(const PixelSpec &copy);
-    INLINE void operator = (const PixelSpec &copy);
 
     INLINE bool operator < (const PixelSpec &other) const;
     INLINE bool operator == (const PixelSpec &other) const;

+ 2 - 1
panda/src/pnmimagetypes/pnmFileTypePfm.cxx

@@ -283,7 +283,8 @@ write_pfm(const PfmFile &pfm) {
     break;
 
   default:
-    nassertr(false, false);
+    nassert_raise("unexpected channel count");
+    return false;
   }
   (*_file) << pfm.get_x_size() << " " << pfm.get_y_size() << "\n";
 

+ 2 - 1
panda/src/pnmimagetypes/pnmFileTypeSGIWriter.cxx

@@ -135,7 +135,8 @@ write_header() {
     break;
 
   default:
-    nassertr(false, false);
+    nassert_raise("unexpected channel count");
+    return false;
   }
 
   // For some reason, we have problems with SGI image files whose pixmax value

+ 1 - 1
panda/src/putil/sparseArray.I

@@ -93,7 +93,7 @@ has_max_num_bits() {
  */
 INLINE int SparseArray::
 get_max_num_bits() {
-  nassertr(false, 0);
+  nassert_raise("SparseArray has no maximum bit count");
   return 0;
 }
 

+ 1 - 1
panda/src/text/textAssembler.cxx

@@ -2536,7 +2536,7 @@ get_primitive(TypeHandle prim_type) {
     return _points;
   }
 
-  nassertr(false, nullptr);
+  nassert_raise("unexpected primitive type");
   return nullptr;
 }
 

+ 0 - 2
panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

@@ -202,8 +202,6 @@ make_geom_munger(const RenderState *state, Thread *current_thread) {
  */
 void TinyGraphicsStateGuardian::
 clear(DrawableRegion *clearable) {
-  PStatTimer timer(_clear_pcollector);
-
   if ((!clearable->get_clear_color_active())&&
       (!clearable->get_clear_depth_active())&&
       (!clearable->get_clear_stencil_active())) {

+ 2 - 1
panda/src/vision/webcamVideoCursorV4L.cxx

@@ -487,7 +487,8 @@ fetch_buffer() {
       block[i + 2] = ex;
     }
 #else
-    nassertr(false /* Not compiled with JPEG support*/, nullptr);
+    nassert_raise("JPEG support not compiled-in");
+    return nullptr;
 #endif
     break;
   }

+ 5 - 0
panda/src/wgldisplay/wglGraphicsWindow.cxx

@@ -87,6 +87,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   wglgsg->reset_if_new();
 
   if (mode == FM_render) {
+    wglgsg->push_group_marker(std::string("wglGraphicsWindow ") + get_name());
     clear_cube_map_selection();
   }
 
@@ -114,6 +115,10 @@ end_frame(FrameMode mode, Thread *current_thread) {
   if (mode == FM_render) {
     trigger_flip();
     clear_cube_map_selection();
+
+    wglGraphicsStateGuardian *wglgsg;
+    DCAST_INTO_V(wglgsg, _gsg);
+    wglgsg->pop_group_marker();
   }
 }
 

+ 5 - 0
panda/src/x11display/config_x11display.cxx

@@ -40,6 +40,11 @@ ConfigVariableBool x_error_abort
           "of an error from the X window system.  This can make it easier "
           "to discover where these errors are generated."));
 
+ConfigVariableBool x_init_threads
+("x-init-threads", false,
+ PRC_DESC("Set this true to ask Panda3D to call XInitThreads() upon loading "
+          "the display module, which may help with some threading issues."));
+
 ConfigVariableInt x_wheel_up_button
 ("x-wheel-up-button", 4,
  PRC_DESC("This is the mouse button index of the wheel_up event: which "

+ 1 - 0
panda/src/x11display/config_x11display.h

@@ -26,6 +26,7 @@ extern EXPCL_PANDAX11 void init_libx11display();
 
 extern ConfigVariableString display_cfg;
 extern ConfigVariableBool x_error_abort;
+extern ConfigVariableBool x_init_threads;
 
 extern ConfigVariableInt x_wheel_up_button;
 extern ConfigVariableInt x_wheel_down_button;

+ 6 - 0
panda/src/x11display/x11GraphicsPipe.cxx

@@ -66,6 +66,12 @@ x11GraphicsPipe(const std::string &display) :
   _im = (XIM)nullptr;
   _hidden_cursor = None;
 
+  // According to the documentation, we should call this before making any
+  // other Xlib calls if we wish to use the Xlib locking system.
+  if (x_init_threads) {
+    XInitThreads();
+  }
+
   install_error_handlers();
 
   _display = XOpenDisplay(display_spec.c_str());

+ 17 - 0
panda/src/x11display/x11GraphicsWindow.cxx

@@ -218,6 +218,23 @@ move_pointer(int device, int x, int y) {
   }
 }
 
+/**
+ * Clears the entire framebuffer before rendering, according to the settings
+ * of get_color_clear_active() and get_depth_clear_active() (inherited from
+ * DrawableRegion).
+ *
+ * This function is called only within the draw thread.
+ */
+void x11GraphicsWindow::
+clear(Thread *current_thread) {
+  if (is_any_clear_active()) {
+    // Evidently the NVIDIA driver may call glXCreateNewContext inside
+    // prepare_display_region, so we need to hold the X11 lock.
+    LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
+    GraphicsOutput::clear(current_thread);
+  }
+}
+
 /**
  * This function will be called within the draw thread before beginning
  * rendering for a given frame.  It should do whatever setup is required, and

+ 2 - 0
panda/src/x11display/x11GraphicsWindow.h

@@ -36,6 +36,8 @@ public:
 
   virtual MouseData get_pointer(int device) const;
   virtual bool move_pointer(int device, int x, int y);
+
+  virtual void clear(Thread *current_thread);
   virtual bool begin_frame(FrameMode mode, Thread *current_thread);
   virtual void end_frame(FrameMode mode, Thread *current_thread);
 

+ 1 - 1
pandatool/src/assimp/pandaIOSystem.cxx

@@ -79,7 +79,7 @@ Open(const char *file, const char *mode) {
     return new PandaIOStream(*stream);
 
   } else {
-    nassertr(false, nullptr); // Not implemented on purpose.
+    nassert_raise("write mode not implemented");
     return nullptr;
   }
 }

+ 1 - 1
pandatool/src/eggcharbase/eggBackPointer.cxx

@@ -40,7 +40,7 @@ get_frame_rate() const {
 void EggBackPointer::
 extend_to(int num_frames) {
   // Whoops, can't extend this kind of table!
-  nassertv(false);
+  nassert_raise("can't extend this kind of table");
 }
 
 /**

+ 21 - 0
tests/pnmimage/test_pnmimage.py

@@ -0,0 +1,21 @@
+from panda3d.core import PNMImage, PNMImageHeader
+
+
+def test_pixelspec_ctor():
+    assert tuple(PNMImage.PixelSpec(1)) == (1, 1, 1, 0)
+    assert tuple(PNMImage.PixelSpec(1, 2)) == (1, 1, 1, 2)
+    assert tuple(PNMImage.PixelSpec(1, 2, 3)) == (1, 2, 3, 0)
+    assert tuple(PNMImage.PixelSpec(1, 2, 3, 4)) == (1, 2, 3, 4)
+
+    assert tuple(PNMImage.PixelSpec((1, 2, 3))) == (1, 2, 3, 0)
+    assert tuple(PNMImage.PixelSpec((1, 2, 3), 4)) == (1, 2, 3, 4)
+
+    # Copy constructor
+    spec = PNMImage.PixelSpec(1, 2, 3, 4)
+    assert tuple(PNMImage.PixelSpec(spec)) == (1, 2, 3, 4)
+
+
+def test_pixelspec_coerce():
+    img = PNMImage(1, 1, 4)
+    img.set_pixel(0, 0, (1, 2, 3, 4))
+    assert img.get_pixel(0, 0) == (1, 2, 3, 4)