Ver Fonte

Merge branch 'master' into input-overhaul

rdb há 8 anos atrás
pai
commit
962e6e31e4
100 ficheiros alterados com 2217 adições e 824 exclusões
  1. 4 4
      direct/src/distributed/cConnectionRepository.cxx
  2. 7 0
      direct/src/showbase/ShowBase.py
  3. 20 7
      direct/src/showbase/ShowBaseGlobal.py
  4. 2 0
      direct/src/task/Task.py
  5. 12 0
      makepanda/installpanda.py
  6. 6 4
      makepanda/makepanda.py
  7. 0 1
      makepanda/makewheel.py
  8. 12 0
      panda/src/cocoadisplay/cocoaGraphicsBuffer.I
  9. 61 0
      panda/src/cocoadisplay/cocoaGraphicsBuffer.h
  10. 165 0
      panda/src/cocoadisplay/cocoaGraphicsBuffer.mm
  11. 0 8
      panda/src/cocoadisplay/cocoaGraphicsPipe.I
  12. 2 8
      panda/src/cocoadisplay/cocoaGraphicsPipe.h
  13. 23 112
      panda/src/cocoadisplay/cocoaGraphicsPipe.mm
  14. 18 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I
  15. 4 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h
  16. 16 1
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm
  17. 32 12
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  18. 2 0
      panda/src/cocoadisplay/config_cocoadisplay.mm
  19. 1 0
      panda/src/cocoadisplay/p3cocoadisplay_composite1.mm
  20. 40 30
      panda/src/display/displayRegion.cxx
  21. 171 105
      panda/src/display/graphicsEngine.cxx
  22. 15 5
      panda/src/display/graphicsEngine.h
  23. 54 92
      panda/src/display/graphicsStateGuardian.cxx
  24. 1 1
      panda/src/display/graphicsStateGuardian.h
  25. 2 2
      panda/src/display/graphicsWindowProcCallbackData.I
  26. 3 3
      panda/src/display/graphicsWindowProcCallbackData.h
  27. 1 1
      panda/src/display/pythonGraphicsWindowProc.cxx
  28. 6 0
      panda/src/display/windowProperties.cxx
  29. 1 0
      panda/src/display/windowProperties.h
  30. 1 1
      panda/src/dxgsg9/dxGraphicsStateGuardian9.I
  31. 2 2
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  32. 1 1
      panda/src/dxgsg9/dxTextureContext9.cxx
  33. 4 4
      panda/src/dxgsg9/wdxGraphicsWindow9.cxx
  34. 98 2
      panda/src/event/asyncFuture.I
  35. 265 84
      panda/src/event/asyncFuture.cxx
  36. 85 20
      panda/src/event/asyncFuture.h
  37. 152 9
      panda/src/event/asyncFuture_ext.cxx
  38. 5 1
      panda/src/event/asyncFuture_ext.h
  39. 1 0
      panda/src/event/asyncTask.h
  40. 2 1
      panda/src/event/asyncTaskChain.cxx
  41. 1 0
      panda/src/event/config_event.cxx
  42. 17 22
      panda/src/event/pythonTask.cxx
  43. 2 1
      panda/src/event/pythonTask.h
  44. 26 0
      panda/src/express/pointerToArray.I
  45. 8 0
      panda/src/express/pointerToArray.h
  46. 20 17
      panda/src/express/pointerToArray_ext.I
  47. 11 7
      panda/src/glstuff/glGeomMunger_src.cxx
  48. 31 14
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  49. 0 2
      panda/src/glstuff/glGraphicsBuffer_src.h
  50. 42 7
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  51. 2 0
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  52. 8 13
      panda/src/gobj/geom.I
  53. 13 21
      panda/src/gobj/geom.cxx
  54. 3 2
      panda/src/gobj/geom.h
  55. 14 2
      panda/src/gobj/geomVertexArrayData.I
  56. 5 12
      panda/src/gobj/geomVertexArrayData.cxx
  57. 2 1
      panda/src/gobj/geomVertexArrayData.h
  58. 7 10
      panda/src/gobj/geomVertexData.I
  59. 2 16
      panda/src/gobj/geomVertexData.cxx
  60. 2 1
      panda/src/gobj/geomVertexData.h
  61. 4 2
      panda/src/gobj/shaderBuffer.I
  62. 8 0
      panda/src/gobj/shaderBuffer.cxx
  63. 2 0
      panda/src/gobj/shaderBuffer.h
  64. 59 4
      panda/src/gobj/texture.I
  65. 105 31
      panda/src/gobj/texture.cxx
  66. 6 2
      panda/src/gobj/texture.h
  67. 55 2
      panda/src/gobj/texturePeeker.cxx
  68. 2 0
      panda/src/gobj/texturePeeker.h
  69. 23 0
      panda/src/gobj/texture_ext.cxx
  70. 16 0
      panda/src/linmath/lmatrix4_src.I
  71. 3 0
      panda/src/linmath/lmatrix4_src.h
  72. 23 0
      panda/src/linmath/lquaternion_src.cxx
  73. 2 0
      panda/src/linmath/lquaternion_src.h
  74. 19 0
      panda/src/linmath/lvecBase4_src.I
  75. 3 0
      panda/src/linmath/lvecBase4_src.h
  76. 0 1
      panda/src/movies/flacAudioCursor.cxx
  77. 1 0
      panda/src/pgraph/textureAttrib.cxx
  78. 16 1
      panda/src/pgraphnodes/lightLensNode.I
  79. 33 7
      panda/src/pgraphnodes/lightLensNode.cxx
  80. 5 1
      panda/src/pgraphnodes/lightLensNode.h
  81. 30 0
      panda/src/pgraphnodes/pointLight.cxx
  82. 2 0
      panda/src/pgraphnodes/pointLight.h
  83. 0 7
      panda/src/pipeline/cycleData.I
  84. 3 1
      panda/src/pipeline/cycleData.h
  85. 2 2
      panda/src/pipeline/pipeline.I
  86. 189 67
      panda/src/pipeline/pipeline.cxx
  87. 10 1
      panda/src/pipeline/pipeline.h
  88. 21 0
      panda/src/pipeline/pipelineCycler.I
  89. 3 1
      panda/src/pipeline/pipelineCycler.h
  90. 2 2
      panda/src/pipeline/pipelineCyclerTrueImpl.I
  91. 3 3
      panda/src/pipeline/pipelineCyclerTrueImpl.cxx
  92. 4 1
      panda/src/pipeline/pipelineCyclerTrueImpl.h
  93. 14 0
      panda/src/pnmimage/convert_srgb.I
  94. 5 0
      panda/src/pnmimage/convert_srgb.h
  95. 1 1
      panda/src/pnmimage/pnmImage.I
  96. 4 2
      panda/src/putil/clockObject.I
  97. 10 11
      panda/src/putil/clockObject.cxx
  98. 2 2
      panda/src/putil/clockObject.h
  99. 13 2
      panda/src/putil/simpleHashMap.I
  100. 1 1
      panda/src/wgldisplay/wglGraphicsWindow.cxx

+ 4 - 4
direct/src/distributed/cConnectionRepository.cxx

@@ -708,7 +708,7 @@ handle_update_field() {
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
 
       // If in quiet zone mode, throw update away unless distobj has
@@ -799,7 +799,7 @@ handle_update_field_owner() {
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
 
       // check if we should forward this update to the owner view
@@ -841,7 +841,7 @@ handle_update_field_owner() {
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
 
       // check if we should forward this update to the owner view
@@ -974,7 +974,7 @@ describe_message(ostream &out, const string &prefix,
         Py_DECREF(dclass_obj);
         nassertv(dclass_this != NULL);
 
-        dclass = (DCClass *)PyLong_AsLong(dclass_this);
+        dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
         Py_DECREF(dclass_this);
       }
     }

+ 7 - 0
direct/src/showbase/ShowBase.py

@@ -401,6 +401,10 @@ class ShowBase(DirectObject.DirectObject):
             builtins.aspect2dp = self.aspect2dp
             builtins.pixel2dp = self.pixel2dp
 
+        # Now add this instance to the ShowBaseGlobal module scope.
+        from . import ShowBaseGlobal
+        ShowBaseGlobal.base = self
+
         if self.__dev__:
             ShowBase.notify.debug('__dev__ == %s' % self.__dev__)
         else:
@@ -526,6 +530,9 @@ class ShowBase(DirectObject.DirectObject):
             del builtins.base
             del builtins.loader
             del builtins.taskMgr
+            ShowBaseGlobal = sys.modules.get('direct.showbase.ShowBaseGlobal', None)
+            if ShowBaseGlobal:
+                del ShowBaseGlobal.base
 
         # [gjeon] restore sticky key settings
         if self.config.GetBool('disable-sticky-keys', 0):

+ 20 - 7
direct/src/showbase/ShowBaseGlobal.py

@@ -1,14 +1,26 @@
-"""instantiate global ShowBase object"""
+"""This module serves as a container to hold the global ShowBase instance, as
+an alternative to using the builtin scope.
+
+Note that you cannot directly import `base` from this module since ShowBase
+may not have been created yet; instead, ShowBase dynamically adds itself to
+this module's scope when instantiated."""
 
 __all__ = []
 
-from .ShowBase import *
+from .ShowBase import ShowBase, WindowControls
+from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
+from panda3d.core import VirtualFileSystem, Notify, ClockObject, PandaSystem
+from panda3d.core import ConfigPageManager, ConfigVariableManager
+from panda3d.direct import get_config_showbase
+
+config = get_config_showbase()
 
-# Create the showbase instance
-# This should be created by the game specific "start" file
-#ShowBase()
-# Instead of creating a show base, assert that one has already been created
-assert base
+vfs = VirtualFileSystem.getGlobalPtr()
+ostream = Notify.out()
+globalClock = ClockObject.getGlobalClock()
+cpMgr = ConfigPageManager.getGlobalPtr()
+cvMgr = ConfigVariableManager.getGlobalPtr()
+pandaSystem = PandaSystem.getGlobalPtr()
 
 # Set direct notify categories now that we have config
 directNotify.setDconfigLevels()
@@ -31,3 +43,4 @@ builtins.inspect = inspect
 if (not __debug__) and __dev__:
     notify = directNotify.newCategory('ShowBaseGlobal')
     notify.error("You must set 'want-dev' to false in non-debug mode.")
+    del notify

+ 2 - 0
direct/src/task/Task.py

@@ -86,6 +86,8 @@ Task.DtoolClassDict['exit'] = exit
 pause = AsyncTaskPause
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 
+gather = Task.gather
+
 def sequence(*taskList):
     seq = AsyncTaskSequence('sequence')
     for task in taskList:

+ 12 - 0
makepanda/installpanda.py

@@ -229,6 +229,14 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
     DeleteBuildFiles(destdir+prefix+"/include/panda3d")
     DeleteEmptyDirs(destdir+prefix+"/include/panda3d")
 
+    # Change permissions on include directory.
+    os.chmod(destdir + prefix + "/include", 0o755)
+    for root, dirs, files in os.walk(destdir + prefix + "/include"):
+        for basename in dirs:
+            os.chmod(os.path.join(root, basename), 0o755)
+        for basename in files:
+            os.chmod(os.path.join(root, basename), 0o644)
+
     # rpmlint doesn't like this file, for some reason.
     if (os.path.isfile(destdir+prefix+"/share/panda3d/direct/leveleditor/copyfiles.pl")):
         os.remove(destdir+prefix+"/share/panda3d/direct/leveleditor/copyfiles.pl")
@@ -276,6 +284,7 @@ if (__name__ == "__main__"):
     parser.add_option('', '--destdir', dest = 'destdir', help = 'Destination directory [default=%s]' % destdir, default = destdir)
     parser.add_option('', '--prefix', dest = 'prefix', help = 'Prefix [default=/usr/local]', default = '/usr/local')
     parser.add_option('', '--runtime', dest = 'runtime', help = 'Specify if runtime build [default=no]', action = 'store_true', default = False)
+    parser.add_option('', '--verbose', dest = 'verbose', help = 'Print commands that are executed [default=no]', action = 'store_true', default = False)
     (options, args) = parser.parse_args()
 
     destdir = options.destdir
@@ -286,6 +295,9 @@ if (__name__ == "__main__"):
     if (destdir != "" and not os.path.isdir(destdir)):
         exit("Directory '%s' does not exist!" % destdir)
 
+    if options.verbose:
+        SetVerbose(True)
+
     if (options.runtime):
         print("Installing Panda3D Runtime into " + destdir + options.prefix)
         InstallRuntime(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir)

+ 6 - 4
makepanda/makepanda.py

@@ -6373,6 +6373,8 @@ for VER in MAYAVERSIONS:
         continue
     elif GetTarget() == 'darwin' and int(VNUM) >= 2009:
       ARCH_OPTS = ['NOARCH:PPC']
+    elif GetTarget() == 'darwin':
+      ARCH_OPTS = ['NOARCH:X86_64']
     else:
       ARCH_OPTS = []
 
@@ -6980,8 +6982,8 @@ def MakeInstallerLinux():
         else:
             InstallPanda(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=lib_dir)
             oscmd("chmod -R 755 targetroot/usr/share/panda3d")
-            oscmd("mkdir -p targetroot/usr/share/man/man1")
-            oscmd("cp doc/man/*.1 targetroot/usr/share/man/man1/")
+            oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1")
+            oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/")
 
         oscmd("dpkg --print-architecture > "+GetOutputDir()+"/tmp/architecture.txt")
         pkg_arch = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip()
@@ -7146,8 +7148,8 @@ def MakeInstallerOSX():
     # Trailing newline is important, works around a bug in OSX
     WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n")
 
-    oscmd("mkdir -p dstroot/tools/usr/local/share/man/man1")
-    oscmd("cp doc/man/*.1 dstroot/tools/usr/local/share/man/man1/")
+    oscmd("mkdir -m 0755 -p dstroot/tools/usr/local/share/man/man1")
+    oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/")
 
     for base in os.listdir(GetOutputDir()+"/bin"):
         binname = "dstroot/tools/Developer/Panda3D/bin/" + base

+ 0 - 1
makepanda/makewheel.py

@@ -343,7 +343,6 @@ class WheelFile(object):
                 # Otherwise, just copy it over.
                 temp.write(open(source_path, 'rb').read())
 
-            temp.write(open(source_path, 'rb').read())
             os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111)
             temp.close()
 

+ 12 - 0
panda/src/cocoadisplay/cocoaGraphicsBuffer.I

@@ -0,0 +1,12 @@
+/**
+ * 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 cocoaGraphicsBuffer.I
+ * @author rdb
+ * @date 2017-12-19
+ */

+ 61 - 0
panda/src/cocoadisplay/cocoaGraphicsBuffer.h

@@ -0,0 +1,61 @@
+/**
+ * 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 cocoaGraphicsBuffer.h
+ * @author rdb
+ * @date 2017-12-19
+ */
+
+#ifndef COCOAGRAPHICSBUFFER_H
+#define COCOAGRAPHICSBUFFER_H
+
+#include "pandabase.h"
+#include "glgsg.h"
+
+/**
+ * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
+ * with Cocoa contexts, so that it can be used without a host window.
+ */
+class CocoaGraphicsBuffer : public GLGraphicsBuffer {
+public:
+  CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                      const string &name,
+                      const FrameBufferProperties &fb_prop,
+                      const WindowProperties &win_prop,
+                      int flags,
+                      GraphicsStateGuardian *gsg,
+                      GraphicsOutput *host);
+
+  virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
+
+protected:
+  virtual void close_buffer();
+  virtual bool open_buffer();
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    GLGraphicsBuffer::init_type();
+    register_type(_type_handle, "CocoaGraphicsBuffer",
+                  GLGraphicsBuffer::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "cocoaGraphicsBuffer.I"
+
+#endif

+ 165 - 0
panda/src/cocoadisplay/cocoaGraphicsBuffer.mm

@@ -0,0 +1,165 @@
+/**
+ * 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 cocoaGraphicsBuffer.mm
+ * @author rdb
+ * @date 2017-12-19
+ */
+
+#include "cocoaGraphicsBuffer.h"
+#include "cocoaGraphicsStateGuardian.h"
+#include "config_cocoadisplay.h"
+#include "cocoaGraphicsPipe.h"
+
+#import <OpenGL/OpenGL.h>
+
+TypeHandle CocoaGraphicsBuffer::_type_handle;
+
+/**
+ *
+ */
+CocoaGraphicsBuffer::
+CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                    const string &name,
+                    const FrameBufferProperties &fb_prop,
+                    const WindowProperties &win_prop,
+                    int flags,
+                    GraphicsStateGuardian *gsg,
+                    GraphicsOutput *host) : // Ignore the host.
+  GLGraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, nullptr)
+{
+}
+
+/**
+ * This function will be called within the draw thread before beginning
+ * rendering for a given frame.  It should do whatever setup is required, and
+ * return true if the frame should be rendered, or false if it should be
+ * skipped.
+ */
+bool CocoaGraphicsBuffer::
+begin_frame(FrameMode mode, Thread *current_thread) {
+  if (_gsg == nullptr) {
+    return false;
+  }
+
+  CocoaGraphicsStateGuardian *cocoagsg;
+  DCAST_INTO_R(cocoagsg, _gsg, false);
+  nassertr(cocoagsg->_context != nil, false);
+
+  // Lock the context and make it current.
+  {
+    PStatTimer timer(_make_current_pcollector, current_thread);
+    cocoagsg->lock_context();
+    [cocoagsg->_context makeCurrentContext];
+  }
+
+  return GLGraphicsBuffer::begin_frame(mode, 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 CocoaGraphicsBuffer::
+end_frame(FrameMode mode, Thread *current_thread) {
+  nassertv(_gsg != nullptr);
+
+  GLGraphicsBuffer::end_frame(mode, current_thread);
+
+  // Release the context.
+  CocoaGraphicsStateGuardian *cocoagsg;
+  DCAST_INTO_V(cocoagsg, _gsg);
+  cocoagsg->unlock_context();
+}
+
+/**
+ * Opens the buffer right now.  Called from the window thread.  Returns true
+ * if the buffer is successfully opened, or false if there was a problem.
+ */
+bool CocoaGraphicsBuffer::
+open_buffer() {
+  CocoaGraphicsPipe *cocoa_pipe;
+  DCAST_INTO_R(cocoa_pipe, _pipe, false);
+
+  // GSG CreationInitialization
+  CocoaGraphicsStateGuardian *cocoagsg;
+  if (_gsg == nullptr) {
+    // There is no old gsg.  Create a new one.
+    cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, nullptr);
+    cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
+    _gsg = cocoagsg;
+  } else {
+    // If the old gsg has the wrong pixel format, create a new one that shares
+    // with the old gsg.
+    DCAST_INTO_R(cocoagsg, _gsg, false);
+    if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) {
+      cocoagsg = new CocoaGraphicsStateGuardian(_engine, _pipe, cocoagsg);
+      cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
+      _gsg = cocoagsg;
+    }
+  }
+
+  FrameBufferProperties desired_props(_fb_properties);
+
+  // Lock the context, so we can safely operate on it.
+  cocoagsg->lock_context();
+
+  // Make the context current and initialize what we need.
+  [cocoagsg->_context makeCurrentContext];
+  [cocoagsg->_context update];
+  cocoagsg->reset_if_new();
+
+  // These properties are determined by choose_pixel_format.
+  _fb_properties.set_force_hardware(cocoagsg->_fbprops.get_force_hardware());
+  _fb_properties.set_force_software(cocoagsg->_fbprops.get_force_software());
+
+  bool success = GLGraphicsBuffer::open_buffer();
+  if (success) {
+    rebuild_bitplanes();
+    if (_needs_rebuild) {
+      // If it still needs rebuild, then something must have gone wrong.
+      success = false;
+    }
+  }
+
+  if (success && !_fb_properties.verify_hardware_software
+      (desired_props, cocoagsg->get_gl_renderer())) {
+    GLGraphicsBuffer::close_buffer();
+    success = false;
+  }
+
+  // Release the context.
+  cocoagsg->unlock_context();
+
+  if (!success) {
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Closes the buffer right now.  Called from the window thread.
+ */
+void CocoaGraphicsBuffer::
+close_buffer() {
+  if (_gsg != nullptr) {
+    CocoaGraphicsStateGuardian *cocoagsg;
+    cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
+
+    if (cocoagsg != nullptr && cocoagsg->_context != nil) {
+      cocoagsg->lock_context();
+      GLGraphicsBuffer::close_buffer();
+      cocoagsg->unlock_context();
+    }
+    _gsg.clear();
+  } else {
+    GLGraphicsBuffer::close_buffer();
+  }
+}

+ 0 - 8
panda/src/cocoadisplay/cocoaGraphicsPipe.I

@@ -18,11 +18,3 @@ INLINE CGDirectDisplayID CocoaGraphicsPipe::
 get_display_id() const {
   return _display;
 }
-
-/**
- * Returns the Cocoa NSScreen pointer associated with this graphics pipe.
- */
-INLINE NSScreen *CocoaGraphicsPipe::
-get_nsscreen() const {
-  return _screen;
-}

+ 2 - 8
panda/src/cocoadisplay/cocoaGraphicsPipe.h

@@ -35,13 +35,10 @@ class FrameBufferProperties;
  */
 class CocoaGraphicsPipe : public GraphicsPipe {
 public:
-  CocoaGraphicsPipe();
-  CocoaGraphicsPipe(CGDirectDisplayID display);
-  CocoaGraphicsPipe(NSScreen *screen);
+  CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   virtual ~CocoaGraphicsPipe();
 
   INLINE CGDirectDisplayID get_display_id() const;
-  INLINE NSScreen *get_nsscreen() const;
 
   virtual string get_interface_name() const;
   static PT(GraphicsPipe) pipe_constructor();
@@ -64,11 +61,8 @@ protected:
 private:
   void load_display_information();
 
-  // _display and _screen refer to the same thing, NSScreen being the tiny
-  // Cocoa wrapper around the Quartz display ID.  NSScreen isn't generally
-  // useful, but we need it when creating the window.
+  // This is the Quartz display identifier.
   CGDirectDisplayID _display;
-  NSScreen *_screen;
 
   friend class CocoaGraphicsWindow;
 

+ 23 - 112
panda/src/cocoadisplay/cocoaGraphicsPipe.mm

@@ -12,7 +12,7 @@
  */
 
 #include "cocoaGraphicsPipe.h"
-// #include "cocoaGraphicsBuffer.h"
+#include "cocoaGraphicsBuffer.h"
 #include "cocoaGraphicsWindow.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaPandaApp.h"
@@ -30,104 +30,32 @@
 
 TypeHandle CocoaGraphicsPipe::_type_handle;
 
-static void init_app() {
-  if (NSApp == nil) {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    [CocoaPandaApp sharedApplication];
-
-#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
-    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
-#endif
-    [NSApp finishLaunching];
-    [NSApp activateIgnoringOtherApps:YES];
-
-    // Put Cocoa into thread-safe mode by spawning a thread which immediately
-    // exits.
-    NSThread* thread = [[NSThread alloc] init];
-    [thread start];
-    [thread autorelease];
-  }
-}
-
 /**
- * Uses the main screen (the one the user is most likely to be working in at
- * the moment).
+ * Takes a CoreGraphics display ID, which defaults to the main display.
  */
 CocoaGraphicsPipe::
-CocoaGraphicsPipe() {
+CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
   _supported_types = OT_window | OT_buffer | OT_texture_buffer;
   _is_valid = true;
 
-  init_app();
-
-  _screen = [NSScreen mainScreen];
-  NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
-  _display = (CGDirectDisplayID) [num longValue];
-
-  _display_width = CGDisplayPixelsWide(_display);
-  _display_height = CGDisplayPixelsHigh(_display);
-  load_display_information();
-
-  cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for main screen "
-    << _screen << " with display ID " << _display << "\n";
-}
-
-/**
- * Takes a CoreGraphics display ID.
- */
-CocoaGraphicsPipe::
-CocoaGraphicsPipe(CGDirectDisplayID display) {
-  _supported_types = OT_window | OT_buffer | OT_texture_buffer;
-  _is_valid = true;
-  _display = display;
-
-  init_app();
-
-  // Iterate over the screens to find the one with our display ID.
-  NSEnumerator *e = [[NSScreen screens] objectEnumerator];
-  while (NSScreen *screen = (NSScreen *) [e nextObject]) {
-    NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
-    if (display == (CGDirectDisplayID) [num longValue]) {
-      _screen = screen;
-      break;
-    }
-  }
-
-  _display_width = CGDisplayPixelsWide(_display);
-  _display_height = CGDisplayPixelsHigh(_display);
-  load_display_information();
+  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
-  cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for screen "
-    << _screen << " with display ID " << _display << "\n";
-}
+  // Put Cocoa into thread-safe mode by spawning a thread which immediately
+  // exits.
+  NSThread* thread = [[NSThread alloc] init];
+  [thread start];
+  [thread autorelease];
 
-/**
- * Takes an NSScreen pointer.
- */
-CocoaGraphicsPipe::
-CocoaGraphicsPipe(NSScreen *screen) {
-  _supported_types = OT_window | OT_buffer | OT_texture_buffer;
-  _is_valid = true;
-
-  init_app();
-
-  if (screen == nil) {
-    _screen = [NSScreen mainScreen];
-  } else {
-    _screen = screen;
-  }
-  NSNumber *num = [[_screen deviceDescription] objectForKey: @"NSScreenNumber"];
-  _display = (CGDirectDisplayID) [num longValue];
+  // We used to also obtain the corresponding NSScreen here, but this causes
+  // the application icon to start bouncing, which may be undesirable for
+  // apps that will never open a window.
 
   _display_width = CGDisplayPixelsWide(_display);
   _display_height = CGDisplayPixelsHigh(_display);
   load_display_information();
 
   cocoadisplay_cat.debug()
-    << "Creating CocoaGraphicsPipe for screen "
-    << _screen << " with display ID " << _display << "\n";
+    << "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
 }
 
 /**
@@ -308,10 +236,12 @@ make_output(const string &name,
                                    flags, gsg, host);
   }
 
-  // Second thing to try: a GLGraphicsBuffer
+  // Second thing to try: a GLGraphicsBuffer.  This requires a context, so if
+  // we don't have a host window, we instead create a CocoaGraphicsBuffer,
+  // which wraps around GLGraphicsBuffer and manages a context.
 
   if (retry == 1) {
-    if (!gl_support_fbo || host == NULL ||
+    if (!gl_support_fbo ||
         (flags & (BF_require_parasite | BF_require_window)) != 0) {
       return NULL;
     }
@@ -334,33 +264,14 @@ make_output(const string &name,
         precertify = true;
       }
     }
-    return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
-                                flags, gsg, host);
-  }
-/*
-  // Third thing to try: a CocoaGraphicsBuffer
-  if (retry == 2) {
-    if (((flags&BF_require_parasite)!=0)||
-        ((flags&BF_require_window)!=0)||
-        ((flags&BF_resizeable)!=0)||
-        ((flags&BF_size_track_host)!=0)||
-        ((flags&BF_can_bind_layered)!=0)) {
-      return NULL;
-    }
-
-    if (!support_rtt) {
-      if (((flags&BF_rtt_cumulative)!=0)||
-          ((flags&BF_can_bind_every)!=0)) {
-        // If we require Render-to-Texture, but can't be sure we support it,
-        // bail.
-        return NULL;
-      }
+    if (host != NULL) {
+      return new GLGraphicsBuffer(engine, this, name, fb_prop, win_prop,
+                                  flags, gsg, host);
+    } else {
+      return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
+                                     flags, gsg, host);
     }
-
-    return new CocoaGraphicsBuffer(engine, this, name, fb_prop, win_prop,
-                                 flags, gsg, host);
   }
-*/
 
   // Nothing else left to try.
   return NULL;

+ 18 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.I

@@ -19,3 +19,21 @@ INLINE const FrameBufferProperties &CocoaGraphicsStateGuardian::
 get_fb_properties() const {
   return _fbprops;
 }
+
+/**
+ * Locks the context.
+ */
+INLINE void CocoaGraphicsStateGuardian::
+lock_context() {
+  nassertv(_context != nil);
+  CGLLockContext((CGLContextObj) [_context CGLContextObj]);
+}
+
+/**
+ * Unlocks the context.
+ */
+INLINE void CocoaGraphicsStateGuardian::
+unlock_context() {
+  nassertv(_context != nil);
+  CGLUnlockContext((CGLContextObj) [_context CGLContextObj]);
+}

+ 4 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -19,6 +19,7 @@
 #include "glgsg.h"
 
 #import <AppKit/NSOpenGL.h>
+#import <OpenGL/OpenGL.h>
 
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -38,6 +39,9 @@ public:
 
   virtual ~CocoaGraphicsStateGuardian();
 
+  INLINE void lock_context();
+  INLINE void unlock_context();
+
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_context;
   FrameBufferProperties _fbprops;

+ 16 - 1
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm

@@ -24,6 +24,10 @@
 #define kCGLRendererIDMatchingMask   0x00FE7F00
 #endif
 
+#ifndef NSAppKitVersionNumber10_7
+#define NSAppKitVersionNumber10_7 1138
+#endif
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 /**
@@ -207,7 +211,8 @@ choose_pixel_format(const FrameBufferProperties &properties,
     attribs.push_back(NSOpenGLPFAAccelerated);
   }
 
-  attribs.push_back(NSOpenGLPFAWindow);
+  // This seems to cause getting a 3.2+ context to fail.
+  //attribs.push_back(NSOpenGLPFAWindow);
 
   if (need_pbuffer) {
     attribs.push_back(NSOpenGLPFAPixelBuffer);
@@ -217,6 +222,16 @@ choose_pixel_format(const FrameBufferProperties &properties,
   attribs.push_back(NSOpenGLPFAScreenMask);
   attribs.push_back(CGDisplayIDToOpenGLDisplayMask(display));
 
+  // Set OpenGL version if a minimum was requested.
+  if (gl_version.size() >= 1 && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) {
+    //NB. There is also NSOpenGLProfileVersion4_1Core, but this seems to cause
+    // a software implementation to be selected on my mac mini running 10.11.
+    if (gl_version[0] >= 4 || (gl_version.size() >= 2 && gl_version[0] == 3 && gl_version[1] >= 2)) {
+      attribs.push_back((NSOpenGLPixelFormatAttribute)99); // NSOpenGLPFAOpenGLProfile
+      attribs.push_back((NSOpenGLPixelFormatAttribute)0x3200); // NSOpenGLProfileVersion3_2Core
+    }
+  }
+
   // End of the array
   attribs.push_back((NSOpenGLPixelFormatAttribute)0);
 

+ 32 - 12
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -65,6 +65,16 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   _fullscreen_mode = NULL;
   _windowed_mode = NULL;
 
+  // Now that we know for sure we want a window, we can create the Cocoa app.
+  // This will cause the application icon to appear and start bouncing.
+  if (NSApp == nil) {
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+#endif
+    [NSApp finishLaunching];
+    [NSApp activateIgnoringOtherApps:YES];
+  }
+
   PT(GraphicsWindowInputDevice) device =
     GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
   _input_devices.push_back(device.p());
@@ -145,7 +155,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   nassertr(_view != nil, false);
 
   // Place a lock on the context.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
   // Set the drawable.
   if (_properties.get_fullscreen()) {
@@ -211,7 +221,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
   CocoaGraphicsStateGuardian *cocoagsg;
   DCAST_INTO_V(cocoagsg, _gsg);
 
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
   if (mode == FM_render) {
     // end_render_texture();
@@ -240,7 +250,7 @@ end_flip() {
     CocoaGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
 
-    CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->lock_context();
 
     // Swap the front and back buffer.
     [cocoagsg->_context flushBuffer];
@@ -248,7 +258,7 @@ end_flip() {
     // Flush the window
     [[_view window] flushWindow];
 
-    CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+    cocoagsg->unlock_context();
   }
   GraphicsWindow::end_flip();
 }
@@ -400,6 +410,16 @@ open_window() {
     }
   }
 
+  // Iterate over the screens to find the one with our display ID.
+  NSScreen *screen;
+  NSEnumerator *e = [[NSScreen screens] objectEnumerator];
+  while (screen = (NSScreen *) [e nextObject]) {
+    NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
+    if (cocoa_pipe->_display == (CGDirectDisplayID) [num longValue]) {
+      break;
+    }
+  }
+
   // Center the window if coordinates were set to -1 or -2 TODO: perhaps in
   // future, in the case of -1, it should use the origin used in a previous
   // run of Panda
@@ -407,7 +427,7 @@ open_window() {
   if (parent_nsview != NULL) {
     container = [parent_nsview bounds];
   } else {
-    container = [cocoa_pipe->_screen frame];
+    container = [screen frame];
     container.origin = NSMakePoint(0, 0);
   }
   int x = _properties.get_x_origin();
@@ -454,7 +474,7 @@ open_window() {
     _window = [[CocoaPandaWindow alloc]
                initWithContentRect: rect
                styleMask:windowStyle
-               screen:cocoa_pipe->_screen
+               screen:screen
                window:this];
 
     if (_window == nil) {
@@ -465,7 +485,7 @@ open_window() {
   }
 
   // Lock the context, so we can safely operate on it.
-  CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->lock_context();
 
   // Create the NSView to render to.
   NSRect rect = NSMakeRect(0, 0, _properties.get_x_size(), _properties.get_y_size());
@@ -589,7 +609,7 @@ open_window() {
   cocoagsg->reset_if_new();
 
   // Release the context.
-  CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+  cocoagsg->unlock_context();
 
   if (!cocoagsg->is_valid()) {
     close_window();
@@ -638,9 +658,9 @@ close_window() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     _gsg.clear();
   }
@@ -1430,9 +1450,9 @@ handle_close_event() {
     cocoagsg = DCAST(CocoaGraphicsStateGuardian, _gsg);
 
     if (cocoagsg != NULL && cocoagsg->_context != nil) {
-      CGLLockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->lock_context();
       [cocoagsg->_context clearDrawable];
-      CGLUnlockContext((CGLContextObj) [cocoagsg->_context CGLContextObj]);
+      cocoagsg->unlock_context();
     }
     _gsg.clear();
   }

+ 2 - 0
panda/src/cocoadisplay/config_cocoadisplay.mm

@@ -12,6 +12,7 @@
  */
 
 #include "config_cocoadisplay.h"
+#include "cocoaGraphicsBuffer.h"
 #include "cocoaGraphicsPipe.h"
 #include "cocoaGraphicsStateGuardian.h"
 #include "cocoaGraphicsWindow.h"
@@ -40,6 +41,7 @@ init_libcocoadisplay() {
   }
   initialized = true;
 
+  CocoaGraphicsBuffer::init_type();
   CocoaGraphicsPipe::init_type();
   CocoaGraphicsStateGuardian::init_type();
   CocoaGraphicsWindow::init_type();

+ 1 - 0
panda/src/cocoadisplay/p3cocoadisplay_composite1.mm

@@ -1,4 +1,5 @@
 #include "config_cocoadisplay.mm"
+#include "cocoaGraphicsBuffer.mm"
 #include "cocoaGraphicsPipe.mm"
 #include "cocoaGraphicsStateGuardian.mm"
 #include "cocoaGraphicsWindow.mm"

+ 40 - 30
panda/src/display/displayRegion.cxx

@@ -84,22 +84,30 @@ DisplayRegion::
  */
 void DisplayRegion::
 cleanup() {
-  set_camera(NodePath());
+  CDStageWriter cdata(_cycler, 0);
+  if (cdata->_camera_node != nullptr) {
+    // We need to tell the old camera we're not using it anymore.
+    cdata->_camera_node->remove_display_region(this);
+  }
+  cdata->_camera_node = nullptr;
+  cdata->_camera = NodePath();
 
-  CDCullWriter cdata(_cycler_cull, true);
-  cdata->_cull_result = NULL;
+  CDCullWriter cdata_cull(_cycler_cull, true);
+  cdata_cull->_cull_result = nullptr;
 }
 
 /**
  * Sets the lens index, allows for multiple lenses to be attached to a camera.
  * This is useful for a variety of setups, such as fish eye rendering.  The
  * default is 0.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_lens_index(int index) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-  CDWriter cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
   cdata->_lens_index = index;
 }
 
@@ -107,12 +115,14 @@ set_lens_index(int index) {
  * Changes the portion of the framebuffer this DisplayRegion corresponds to.
  * The parameters range from 0 to 1, where 0,0 is the lower left corner and
  * 1,1 is the upper right; (0, 1, 0, 1) represents the whole screen.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_dimensions(int i, const LVecBase4 &dimensions) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-  CDWriter cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
 
   cdata->_regions[i]._dimensions = dimensions;
 
@@ -145,15 +155,13 @@ is_stereo() const {
  *
  * The camera is actually set via a NodePath, which clarifies which instance
  * of the camera (if there happen to be multiple instances) we should use.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_camera(const NodePath &camera) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-
-  // We allow set_camera(NodePath()) to happen in cleanup(), which can be
-  // called from any pipeline stage.
-  nassertv(pipeline_stage == 0 || camera.is_empty());
-  CDStageWriter cdata(_cycler, 0);
+  CDWriter cdata(_cycler, true);
 
   Camera *camera_node = (Camera *)NULL;
   if (!camera.is_empty()) {
@@ -181,16 +189,17 @@ set_camera(const NodePath &camera) {
 /**
  * Sets the active flag associated with the DisplayRegion.  If the
  * DisplayRegion is marked inactive, nothing is rendered.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_active(bool active) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-  CDLockedReader cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
 
   if (active != cdata->_active) {
-    CDWriter cdataw(_cycler, cdata);
-    cdataw->_active = active;
+    cdata->_active = active;
     win_display_regions_changed();
   }
 }
@@ -199,15 +208,17 @@ set_active(bool active) {
  * Sets the sort value associated with the DisplayRegion.  Within a window,
  * DisplayRegions will be rendered in order from the lowest sort value to the
  * highest.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_sort(int sort) {
-  nassertv(Thread::get_current_pipeline_stage() == 0);
-  CDLockedReader cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
 
   if (sort != cdata->_sort) {
-    CDWriter cdataw(_cycler, cdata);
-    cdataw->_sort = sort;
+    cdata->_sort = sort;
     win_display_regions_changed();
   }
 }
@@ -332,12 +343,14 @@ get_cull_traverser() {
  *
  * This is particularly useful when rendering cube maps and/or stereo
  * textures.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
  */
 void DisplayRegion::
 set_target_tex_page(int page) {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-  CDWriter cdata(_cycler);
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
   cdata->_target_tex_page = page;
 }
 
@@ -555,9 +568,6 @@ compute_pixels() {
  */
 void DisplayRegion::
 compute_pixels_all_stages() {
-  int pipeline_stage = Thread::get_current_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
   if (_window != (GraphicsOutput *)NULL) {
     OPEN_ITERATE_ALL_STAGES(_cycler) {
       CDStageWriter cdata(_cycler, pipeline_stage);

+ 171 - 105
panda/src/display/graphicsEngine.cxx

@@ -153,7 +153,6 @@ GraphicsEngine(Pipeline *pipeline) :
 
   _windows_sorted = true;
   _window_sort_index = 0;
-  _needs_open_windows = false;
 
   set_threading_model(GraphicsThreadingModel(threading_model));
   if (!_threading_model.is_default()) {
@@ -326,13 +325,10 @@ make_output(GraphicsPipe *pipe,
 
   // Sanity check everything.
 
-  GraphicsThreadingModel threading_model = get_threading_model();
   nassertr(pipe != (GraphicsPipe *)NULL, NULL);
   if (gsg != (GraphicsStateGuardian *)NULL) {
     nassertr(pipe == gsg->get_pipe(), NULL);
     nassertr(this == gsg->get_engine(), NULL);
-    nassertr(threading_model.get_draw_name() ==
-             gsg->get_threading_model().get_draw_name(), NULL);
   }
 
   // Are we really asking for a callback window?
@@ -346,8 +342,8 @@ make_output(GraphicsPipe *pipe,
     if (this_gsg != (GraphicsStateGuardian *)NULL) {
       CallbackGraphicsWindow *window = new CallbackGraphicsWindow(this, pipe, name, fb_prop, win_prop, flags, this_gsg);
       window->_sort = sort;
-      do_add_window(window, threading_model);
-      do_add_gsg(window->get_gsg(), pipe, threading_model);
+      do_add_window(window);
+      do_add_gsg(window->get_gsg(), pipe);
       display_cat.info() << "Created output of type CallbackGraphicsWindow\n";
       return window;
     }
@@ -386,8 +382,8 @@ make_output(GraphicsPipe *pipe,
       (host->get_fb_properties().subsumes(fb_prop))) {
     ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
     buffer->_sort = sort;
-    do_add_window(buffer, threading_model);
-    do_add_gsg(host->get_gsg(), pipe, threading_model);
+    do_add_window(buffer);
+    do_add_gsg(host->get_gsg(), pipe);
     display_cat.info() << "Created output of type ParasiteBuffer\n";
     return buffer;
   }
@@ -398,8 +394,8 @@ make_output(GraphicsPipe *pipe,
   if (force_parasite_buffer && can_use_parasite) {
     ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
     buffer->_sort = sort;
-    do_add_window(buffer, threading_model);
-    do_add_gsg(host->get_gsg(), pipe, threading_model);
+    do_add_window(buffer);
+    do_add_gsg(host->get_gsg(), pipe);
     display_cat.info() << "Created output of type ParasiteBuffer\n";
     return buffer;
   }
@@ -412,17 +408,15 @@ make_output(GraphicsPipe *pipe,
       pipe->make_output(name, fb_prop, win_prop, flags, this, gsg, host, retry, precertify);
     if (window != (GraphicsOutput *)NULL) {
       window->_sort = sort;
-      if ((precertify) && (gsg != 0) && (window->get_gsg()==gsg)) {
-        do_add_window(window, threading_model);
-        do_add_gsg(window->get_gsg(), pipe, threading_model);
+      if (precertify && gsg != nullptr && window->get_gsg() == gsg) {
+        do_add_window(window);
         display_cat.info()
           << "Created output of type " << window->get_type() << "\n";
         return window;
       }
-      do_add_window(window, threading_model);
+      do_add_window(window);
       open_windows();
       if (window->is_valid()) {
-        do_add_gsg(window->get_gsg(), pipe, threading_model);
         display_cat.info()
           << "Created output of type " << window->get_type() << "\n";
 
@@ -462,8 +456,8 @@ make_output(GraphicsPipe *pipe,
   if (can_use_parasite) {
     ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
     buffer->_sort = sort;
-    do_add_window(buffer, threading_model);
-    do_add_gsg(host->get_gsg(), pipe, threading_model);
+    do_add_window(buffer);
+    do_add_gsg(host->get_gsg(), pipe);
     display_cat.info() << "Created output of type ParasiteBuffer\n";
     return buffer;
   }
@@ -479,30 +473,24 @@ make_output(GraphicsPipe *pipe,
  * shouldn't be called by user code as make_output normally does this under
  * the hood; it may be useful in esoteric cases in which a custom window
  * object is used.
+ *
+ * This can be called during the rendering loop, unlike make_output(); the
+ * window will be opened before the next frame begins rendering.  Because it
+ * doesn't call open_windows(), however, it's not guaranteed that the window
+ * will succeed opening even if it returns true.
  */
 bool GraphicsEngine::
 add_window(GraphicsOutput *window, int sort) {
-  nassertr(window != NULL, false);
-
-  GraphicsThreadingModel threading_model = get_threading_model();
+  nassertr(window != nullptr, false);
   nassertr(this == window->get_engine(), false);
 
   window->_sort = sort;
-  do_add_window(window, threading_model);
-
-  open_windows();
-  if (window->is_valid()) {
-    do_add_gsg(window->get_gsg(), window->get_pipe(), threading_model);
+  do_add_window(window);
 
-    display_cat.info()
-      << "Added output of type " << window->get_type() << "\n";
-
-    return true;
+  display_cat.info()
+    << "Added output of type " << window->get_type() << "\n";
 
-  } else {
-    remove_window(window);
-    return false;
-  }
+  return true;
 }
 
 /**
@@ -537,6 +525,17 @@ remove_window(GraphicsOutput *window) {
     }
     count = _windows.erase(ptwin);
   }
+
+  // Also check whether it is in _new_windows.
+  {
+    MutexHolder new_windows_holder(_new_windows_lock, current_thread);
+    size_t old_size = _new_windows.size();
+    _new_windows.erase(std::remove(_new_windows.begin(), _new_windows.end(), ptwin), _new_windows.end());
+    if (count == 0 && _new_windows.size() < old_size) {
+      count = 1;
+    }
+  }
+
   if (count == 0) {
     // Never heard of this window.  Do nothing.
     return false;
@@ -601,6 +600,11 @@ remove_all_windows() {
     }
   }
 
+  {
+    MutexHolder new_windows_holder(_new_windows_lock, current_thread);
+    _new_windows.clear();
+  }
+
   _app.do_close(this, current_thread);
   _app.do_pending(this, current_thread);
   terminate_threads(current_thread);
@@ -694,14 +698,12 @@ render_frame() {
   }
 #endif
 
-  if (_needs_open_windows) {
-    // Make sure our buffers and windows are fully realized before we render a
-    // frame.  We do this particularly to realize our offscreen buffers, so
-    // that we don't render a frame before the offscreen buffers are ready
-    // (which might result in a frame going by without some textures having
-    // been rendered).
-    open_windows();
-  }
+  // Make sure our buffers and windows are fully realized before we render a
+  // frame.  We do this particularly to realize our offscreen buffers, so
+  // that we don't render a frame before the offscreen buffers are ready
+  // (which might result in a frame going by without some textures having
+  // been rendered).
+  open_windows();
 
   ClockObject *global_clock = ClockObject::get_global_clock();
 
@@ -945,10 +947,58 @@ open_windows() {
 
   ReMutexHolder holder(_lock, current_thread);
 
-  if (!_windows_sorted) {
-    do_resort_windows();
+  pvector<PT(GraphicsOutput)> new_windows;
+  {
+    MutexHolder new_windows_holder(_new_windows_lock, current_thread);
+    if (_new_windows.empty()) {
+      return;
+    }
+
+    for (auto it = _new_windows.begin(); it != _new_windows.end(); ++it) {
+      GraphicsOutput *window = *it;
+
+      WindowRenderer *cull =
+        get_window_renderer(_threading_model.get_cull_name(),
+                            _threading_model.get_cull_stage());
+      WindowRenderer *draw =
+        get_window_renderer(_threading_model.get_draw_name(),
+                            _threading_model.get_draw_stage());
+
+      if (_threading_model.get_cull_sorting()) {
+        cull->add_window(cull->_cull, window);
+        draw->add_window(draw->_draw, window);
+      } else {
+        cull->add_window(cull->_cdraw, window);
+      }
+
+      // Ask the pipe which thread it prefers to run its windowing commands in
+      // (the "window thread").  This is the thread that handles the commands
+      // to open, resize, etc.  the window.  X requires this to be done in the
+      // app thread (along with all the other windows, since X is strictly
+      // single-threaded), but Windows requires this to be done in draw
+      // (because once an OpenGL context has been bound in a given thread, it
+      // cannot subsequently be bound in any other thread, and we have to bind
+      // a context in open_window()).
+
+      switch (window->get_pipe()->get_preferred_window_thread()) {
+      case GraphicsPipe::PWT_app:
+        _app.add_window(_app._window, window);
+        break;
+
+      case GraphicsPipe::PWT_draw:
+        draw->add_window(draw->_window, window);
+        break;
+      }
+
+      _windows.push_back(window);
+    }
+
+    // Steal the list, since remove_window() may remove from _new_windows.
+    new_windows.swap(_new_windows);
   }
 
+  do_resort_windows();
+
   // We do it twice, to allow both cull and draw to process the window.
   for (int i = 0; i < 2; ++i) {
     _app.do_windows(this, current_thread);
@@ -970,7 +1020,15 @@ open_windows() {
     }
   }
 
-  _needs_open_windows = false;
+  // Now go through the list again to check whether they opened successfully.
+  for (auto it = new_windows.begin(); it != new_windows.end(); ++it) {
+    GraphicsOutput *window = *it;
+    if (window->is_valid()) {
+      do_add_gsg(window->get_gsg(), window->get_pipe());
+    } else {
+      remove_window(window);
+    }
+  }
 }
 
 /**
@@ -1060,17 +1118,34 @@ extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) {
     // has finished its current task.
     WindowRenderer *wr = get_window_renderer(draw_name, 0);
     RenderThread *thread = (RenderThread *)wr;
-    MutexHolder holder2(thread->_cv_mutex);
+    MutexHolder cv_holder(thread->_cv_mutex);
 
     while (thread->_thread_state != TS_wait) {
       thread->_cv_done.wait();
     }
 
-    // OK, now the draw thread is idle.  That's really good enough for our
-    // purposes; we don't *actually* need to make the draw thread do the work
-    // --it's sufficient that it's not doing anything else while we access the
-    // GSG.
-    return gsg->extract_texture_data(tex);
+    // Temporarily set this so that it accesses data from the current thread.
+    int pipeline_stage = Thread::get_current_pipeline_stage();
+    int draw_pipeline_stage = thread->get_pipeline_stage();
+    thread->set_pipeline_stage(pipeline_stage);
+
+    // Now that the draw thread is idle, signal it to do the extraction task.
+    thread->_gsg = gsg;
+    thread->_texture = tex;
+    thread->_thread_state = TS_do_extract;
+    thread->_cv_start.notify();
+    thread->_cv_mutex.release();
+    thread->_cv_mutex.acquire();
+
+    //XXX is this necessary, or is acquiring the mutex enough?
+    while (thread->_thread_state != TS_wait) {
+      thread->_cv_done.wait();
+    }
+
+    thread->set_pipeline_stage(draw_pipeline_stage);
+    thread->_gsg = nullptr;
+    thread->_texture = nullptr;
+    return thread->_result;
   }
 }
 
@@ -1090,6 +1165,7 @@ extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) {
 void GraphicsEngine::
 dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg) {
   nassertv(sattr->get_shader() != (Shader *)NULL);
+  nassertv(gsg != nullptr);
 
   ReMutexHolder holder(_lock);
 
@@ -1098,26 +1174,43 @@ 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->set_state_and_transform(state, TransformState::make_identity());
+    gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
 
   } else {
     // A multi-threaded environment.  We have to wait until the draw thread
     // has finished its current task.
     WindowRenderer *wr = get_window_renderer(draw_name, 0);
     RenderThread *thread = (RenderThread *)wr;
-    MutexHolder holder2(thread->_cv_mutex);
+    MutexHolder cv_holder(thread->_cv_mutex);
 
     while (thread->_thread_state != TS_wait) {
       thread->_cv_done.wait();
     }
 
-    // OK, now the draw thread is idle.  That's really good enough for our
-    // purposes; we don't *actually* need to make the draw thread do the work
-    // --it's sufficient that it's not doing anything else while we access the
-    // GSG.
-  }
+    // Temporarily set this so that it accesses data from the current thread.
+    int pipeline_stage = Thread::get_current_pipeline_stage();
+    int draw_pipeline_stage = thread->get_pipeline_stage();
+    thread->set_pipeline_stage(pipeline_stage);
+
+    // Now that the draw thread is idle, signal it to do the compute task.
+    thread->_gsg = gsg;
+    thread->_state = state.p();
+    thread->_work_groups = work_groups;
+    thread->_thread_state = TS_do_compute;
+    thread->_cv_start.notify();
+    thread->_cv_mutex.release();
+    thread->_cv_mutex.acquire();
 
-  gsg->set_state_and_transform(state, TransformState::make_identity());
-  gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
+    //XXX is this necessary, or is acquiring the mutex enough?
+    while (thread->_thread_state != TS_wait) {
+      thread->_cv_done.wait();
+    }
+
+    thread->set_pipeline_stage(draw_pipeline_stage);
+    thread->_gsg = nullptr;
+    thread->_state = nullptr;
+  }
 }
 
 /**
@@ -1927,10 +2020,10 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
  * list of windows, and to request that the window be opened.
  */
 void GraphicsEngine::
-do_add_window(GraphicsOutput *window,
-              const GraphicsThreadingModel &threading_model) {
-  nassertv(window != NULL);
-  ReMutexHolder holder(_lock);
+do_add_window(GraphicsOutput *window) {
+  nassertv(window != nullptr);
+
+  MutexHolder holder(_new_windows_lock);
   nassertv(window->get_engine() == this);
 
   // We have a special counter that is unique per window that allows us to
@@ -1938,50 +2031,13 @@ do_add_window(GraphicsOutput *window,
   window->_internal_sort_index = _window_sort_index;
   ++_window_sort_index;
 
-  _windows_sorted = false;
-  _windows.push_back(window);
-
-  WindowRenderer *cull =
-    get_window_renderer(threading_model.get_cull_name(),
-                        threading_model.get_cull_stage());
-  WindowRenderer *draw =
-    get_window_renderer(threading_model.get_draw_name(),
-                        threading_model.get_draw_stage());
-
-  if (threading_model.get_cull_sorting()) {
-    cull->add_window(cull->_cull, window);
-    draw->add_window(draw->_draw, window);
-  } else {
-    cull->add_window(cull->_cdraw, window);
-  }
-
-/*
- * Ask the pipe which thread it prefers to run its windowing commands in (the
- * "window thread").  This is the thread that handles the commands to open,
- * resize, etc.  the window.  X requires this to be done in the app thread
- * (along with all the other windows, since X is strictly single-threaded),
- * but Windows requires this to be done in draw (because once an OpenGL
- * context has been bound in a given thread, it cannot subsequently be bound
- * in any other thread, and we have to bind a context in open_window()).
- */
-
-  switch (window->get_pipe()->get_preferred_window_thread()) {
-  case GraphicsPipe::PWT_app:
-    _app.add_window(_app._window, window);
-    break;
-
-  case GraphicsPipe::PWT_draw:
-    draw->add_window(draw->_window, window);
-    break;
-  }
-
   if (display_cat.is_debug()) {
     display_cat.debug()
       << "Created " << window->get_type() << " " << (void *)window << "\n";
   }
 
   window->request_open();
-  _needs_open_windows = true;
+  _new_windows.push_back(window);
 }
 
 /**
@@ -1990,13 +2046,12 @@ do_add_window(GraphicsOutput *window,
  * variables based on the gsg's capabilities.
  */
 void GraphicsEngine::
-do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
-           const GraphicsThreadingModel &threading_model) {
+do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe) {
   nassertv(gsg != NULL);
 
   ReMutexHolder holder(_lock);
   nassertv(gsg->get_pipe() == pipe && gsg->get_engine() == this);
-  gsg->_threading_model = threading_model;
+  gsg->_threading_model = _threading_model;
   if (!_default_loader.is_null()) {
     gsg->set_loader(_default_loader);
   }
@@ -2004,8 +2059,8 @@ do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
   auto_adjust_capabilities(gsg);
 
   WindowRenderer *draw =
-    get_window_renderer(threading_model.get_draw_name(),
-                        threading_model.get_draw_stage());
+    get_window_renderer(_threading_model.get_draw_name(),
+                        _threading_model.get_draw_stage());
 
   draw->add_gsg(gsg);
 }
@@ -2564,6 +2619,17 @@ thread_main() {
       do_pending(_engine, current_thread);
       break;
 
+    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]);
+      break;
+
+    case TS_do_extract:
+      nassertd(_gsg != nullptr && _texture != nullptr) break;
+      _result = _gsg->extract_texture_data(_texture);
+      break;
+
     case TS_terminate:
       do_pending(_engine, current_thread);
       do_close(_engine, current_thread);

+ 15 - 5
panda/src/display/graphicsEngine.h

@@ -123,6 +123,8 @@ public:
     TS_do_flip,
     TS_do_release,
     TS_do_windows,
+    TS_do_compute,
+    TS_do_extract,
     TS_terminate,
     TS_done
   };
@@ -166,10 +168,8 @@ private:
   void do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg,
                DisplayRegion *dr, Thread *current_thread);
 
-  void do_add_window(GraphicsOutput *window,
-                     const GraphicsThreadingModel &threading_model);
-  void do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
-                  const GraphicsThreadingModel &threading_model);
+  void do_add_window(GraphicsOutput *window);
+  void do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe);
   void do_remove_window(GraphicsOutput *window, Thread *current_thread);
   void do_resort_windows();
   void terminate_threads(Thread *current_thread);
@@ -301,6 +301,13 @@ private:
     ConditionVar _cv_start;
     ConditionVar _cv_done;
     ThreadState _thread_state;
+
+    // These are stored for extract_texture_data and dispatch_compute.
+    GraphicsStateGuardian *_gsg;
+    Texture *_texture;
+    const RenderState *_state;
+    LVecBase3i _work_groups;
+    bool _result;
   };
 
   WindowRenderer *get_window_renderer(const string &name, int pipeline_stage);
@@ -308,8 +315,11 @@ private:
   Pipeline *_pipeline;
   Windows _windows;
   bool _windows_sorted;
+
+  // This lock protects the next two fields.
+  Mutex _new_windows_lock;
   unsigned int _window_sort_index;
-  bool _needs_open_windows;
+  pvector<PT(GraphicsOutput)> _new_windows;
 
   WindowRenderer _app;
   typedef pmap<string, PT(RenderThread) > Threads;

+ 54 - 92
panda/src/display/graphicsStateGuardian.cxx

@@ -3231,9 +3231,10 @@ async_reload_texture(TextureContext *tc) {
 PT(Texture) GraphicsStateGuardian::
 get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) {
   PandaNode *node = light_np.node();
+  bool is_point = node->is_of_type(PointLight::get_class_type());
   nassertr(node->is_of_type(DirectionalLight::get_class_type()) ||
-           node->is_of_type(PointLight::get_class_type()) ||
-           node->is_of_type(Spotlight::get_class_type()), NULL);
+           node->is_of_type(Spotlight::get_class_type()) ||
+           is_point, nullptr);
 
   LightLensNode *light = (LightLensNode *)node;
   if (light == nullptr || !light->_shadow_caster) {
@@ -3246,20 +3247,49 @@ get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) {
     }
   }
 
+  // The light's shadow map should have been created by set_shadow_caster().
+  nassertr(light->_shadow_map != nullptr, nullptr);
+
   // See if we already have a buffer.  If not, create one.
-  if (light->_sbuffers.count(this) == 0) {
-    if (host == (GraphicsOutputBase *)NULL) {
-      host = _current_display_region->get_window();
-    }
-    nassertr(host != NULL, NULL);
+  if (light->_sbuffers.count(this) != 0) {
+    // There's already a buffer - use that.
+    return light->_shadow_map;
+  }
+
+  if (display_cat.is_debug()) {
+    display_cat.debug()
+      << "Constructing shadow buffer for light '" << light->get_name()
+      << "', size=" << light->_sb_size[0] << "x" << light->_sb_size[1]
+      << ", sort=" << light->_sb_sort << "\n";
+  }
 
-    // Nope, the light doesn't have a buffer for our GSG. Make one.
-    return make_shadow_buffer(light_np, host);
+  if (host == nullptr) {
+    nassertr(_current_display_region != nullptr, nullptr);
+    host = _current_display_region->get_window();
+  }
+  nassertr(host != nullptr, nullptr);
 
+  // Nope, the light doesn't have a buffer for our GSG. Make one.
+  GraphicsOutput *sbuffer = make_shadow_buffer(light, light->_shadow_map,
+                                               DCAST(GraphicsOutput, host));
+
+  // Assign display region(s) to the buffer and camera
+  if (is_point) {
+    for (int i = 0; i < 6; ++i) {
+      PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
+      dr->set_lens_index(i);
+      dr->set_target_tex_page(i);
+      dr->set_camera(light_np);
+      dr->set_clear_depth_active(true);
+    }
   } else {
-    // There's already a buffer - use that.
-    return light->_sbuffers[this]->get_texture();
+    PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
+    dr->set_camera(light_np);
+    dr->set_clear_depth_active(true);
   }
+
+  light->_sbuffers[this] = sbuffer;
+  return light->_shadow_map;
 }
 
 /**
@@ -3299,101 +3329,33 @@ get_dummy_shadow_map(Texture::TextureType texture_type) const {
 }
 
 /**
- * Creates a depth buffer for shadow mapping.  This is a convenience function
- * for the ShaderGenerator; putting this directly in the ShaderGenerator would
- * cause circular dependency issues.  Returns the depth texture.
+ * Creates a depth buffer for shadow mapping.  A derived GSG can override this
+ * if it knows that a particular buffer type works best for shadow rendering.
  */
-PT(Texture) GraphicsStateGuardian::
-make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) {
-  // Make sure everything is valid.
-  PandaNode *node = light_np.node();
-  nassertr(node->is_of_type(DirectionalLight::get_class_type()) ||
-           node->is_of_type(PointLight::get_class_type()) ||
-           node->is_of_type(Spotlight::get_class_type()), NULL);
-
-  LightLensNode *light = (LightLensNode *)node;
-  if (light == NULL || !light->_shadow_caster) {
-    return NULL;
-  }
-
+GraphicsOutput *GraphicsStateGuardian::
+make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host) {
   bool is_point = light->is_of_type(PointLight::get_class_type());
 
-  nassertr(light->_sbuffers.count(this) == 0, NULL);
-
-  if (display_cat.is_debug()) {
-    display_cat.debug()
-      << "Constructing shadow buffer for light '" << light->get_name()
-      << "', size=" << light->_sb_size[0] << "x" << light->_sb_size[1]
-      << ", sort=" << light->_sb_sort << "\n";
-  }
-
   // Determine the properties for creating the depth buffer.
   FrameBufferProperties fbp;
   fbp.set_depth_bits(shadow_depth_bits);
 
-  WindowProperties props = WindowProperties::size(light->_sb_size[0], light->_sb_size[1]);
+  WindowProperties props = WindowProperties::size(light->_sb_size);
   int flags = GraphicsPipe::BF_refuse_window;
   if (is_point) {
     flags |= GraphicsPipe::BF_size_square;
   }
 
-  // Create the buffer
-  PT(GraphicsOutput) sbuffer = get_engine()->make_output(get_pipe(), light->get_name(),
-      light->_sb_sort, fbp, props, flags, this, DCAST(GraphicsOutput, host));
-  nassertr(sbuffer != NULL, NULL);
-
-  // Create a texture and fill it in with some data to workaround an OpenGL
-  // error
-  PT(Texture) tex = new Texture(light->get_name());
-  if (is_point) {
-    if (light->_sb_size[0] != light->_sb_size[1]) {
-      display_cat.error()
-        << "PointLight shadow buffers must have an equal width and height!\n";
-    }
-    tex->setup_cube_map(light->_sb_size[0], Texture::T_unsigned_byte, Texture::F_depth_component);
-  } else {
-    tex->setup_2d_texture(light->_sb_size[0], light->_sb_size[1], Texture::T_unsigned_byte, Texture::F_depth_component);
-  }
-  tex->make_ram_image();
-  sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
-
-  // Set the wrap mode
-  if (is_point) {
-    tex->set_wrap_u(SamplerState::WM_clamp);
-    tex->set_wrap_v(SamplerState::WM_clamp);
-  } else {
-    tex->set_wrap_u(SamplerState::WM_border_color);
-    tex->set_wrap_v(SamplerState::WM_border_color);
-    tex->set_border_color(LVecBase4(1, 1, 1, 1));
-  }
+  // Create the buffer.  This is a bit tricky because make_output() can only
+  // be called from the app thread, but it won't cause issues as long as the
+  // pipe can precertify the buffer, which it can in most cases.
+  GraphicsOutput *sbuffer = get_engine()->make_output(get_pipe(),
+    light->get_name(), light->_sb_sort, fbp, props, flags, this, host);
 
-  // Note: cube map shadow filtering doesn't seem to work in Cg.
-  if (get_supports_shadow_filter() && !is_point) {
-    // If we have the ARB_shadow extension, enable shadow filtering.
-    tex->set_minfilter(SamplerState::FT_shadow);
-    tex->set_magfilter(SamplerState::FT_shadow);
-  } else {
-    tex->set_minfilter(SamplerState::FT_linear);
-    tex->set_magfilter(SamplerState::FT_linear);
+  if (sbuffer != nullptr) {
+    sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
   }
-
-  // Assign display region(s) to the buffer and camera
-  if (is_point) {
-    for (int i = 0; i < 6; ++i) {
-      PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
-      dr->set_lens_index(i);
-      dr->set_target_tex_page(i);
-      dr->set_camera(light_np);
-      dr->set_clear_depth_active(true);
-    }
-  } else {
-    PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
-    dr->set_camera(light_np);
-    dr->set_clear_depth_active(true);
-  }
-  light->_sbuffers[this] = sbuffer;
-
-  return tex;
+  return sbuffer;
 }
 
 /**

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

@@ -424,7 +424,7 @@ public:
 
   PT(Texture) get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host=NULL);
   PT(Texture) get_dummy_shadow_map(Texture::TextureType texture_type) const;
-  PT(Texture) make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host);
+  virtual GraphicsOutput *make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host);
 
   virtual void ensure_generated_shader(const RenderState *state);
 

+ 2 - 2
panda/src/display/graphicsWindowProcCallbackData.I

@@ -32,7 +32,7 @@ get_graphics_window() const {
 /**
  * Returns the Windows proc hwnd parameter.
  */
-INLINE int GraphicsWindowProcCallbackData::
+INLINE uintptr_t GraphicsWindowProcCallbackData::
 get_hwnd() const {
   return _hwnd;
 }
@@ -65,7 +65,7 @@ get_lparam() const {
  * Sets the Windows proc hwnd parameter.
  */
 INLINE void GraphicsWindowProcCallbackData::
-set_hwnd(int hwnd) {
+set_hwnd(uintptr_t hwnd) {
   _hwnd = hwnd;
 }
 

+ 3 - 3
panda/src/display/graphicsWindowProcCallbackData.h

@@ -32,7 +32,7 @@ public:
   INLINE GraphicsWindow* get_graphics_window() const;
 
 #ifdef WIN32
-  INLINE void set_hwnd(int hwnd);
+  INLINE void set_hwnd(uintptr_t hwnd);
   INLINE void set_msg(int msg);
   INLINE void set_wparam(int wparam);
   INLINE void set_lparam(int lparam);
@@ -42,7 +42,7 @@ PUBLISHED:
   virtual void output(ostream &out) const;
 
 #ifdef WIN32
-  INLINE int get_hwnd() const;
+  INLINE uintptr_t get_hwnd() const;
   INLINE int get_msg() const;
   INLINE int get_wparam() const;
   INLINE int get_lparam() const;
@@ -55,7 +55,7 @@ PUBLISHED:
 private:
   GraphicsWindow* _graphicsWindow;
 #ifdef WIN32
-  int _hwnd;
+  uintptr_t _hwnd;
   int _msg;
   int _wparam;
   int _lparam;

+ 1 - 1
panda/src/display/pythonGraphicsWindowProc.cxx

@@ -47,7 +47,7 @@ PythonGraphicsWindowProc::
 LONG PythonGraphicsWindowProc::
 wnd_proc(GraphicsWindow* graphicsWindow, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){
   GraphicsWindowProcCallbackData cdata(graphicsWindow);
-  cdata.set_hwnd((int)hwnd);
+  cdata.set_hwnd((uintptr_t)hwnd);
   cdata.set_msg(msg);
   cdata.set_wparam(wparam);
   cdata.set_lparam(lparam);

+ 6 - 0
panda/src/display/windowProperties.cxx

@@ -133,6 +133,12 @@ clear_default() {
  * size is the only property that matters to buffers.
  */
 WindowProperties WindowProperties::
+size(const LVecBase2i &size) {
+  WindowProperties props;
+  props.set_size(size);
+  return props;
+}
+WindowProperties WindowProperties::
 size(int x_size, int y_size) {
   WindowProperties props;
   props.set_size(x_size, y_size);

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

@@ -52,6 +52,7 @@ PUBLISHED:
   MAKE_PROPERTY(config_properties, get_config_properties);
   MAKE_PROPERTY(default, get_default, set_default);
 
+  static WindowProperties size(const LVecBase2i &size);
   static WindowProperties size(int x_size, int y_size);
 
   bool operator == (const WindowProperties &other) const;

+ 1 - 1
panda/src/dxgsg9/dxGraphicsStateGuardian9.I

@@ -127,7 +127,7 @@ get_safe_buffer_start() {
     // buffer, and then pointing to the first multiple of 0x10000 within that
     // buffer.
     _temp_buffer = new unsigned char[0x1ffff];
-    _safe_buffer_start = (unsigned char *)(((long)_temp_buffer + 0xffff) & ~0xffff);
+    _safe_buffer_start = (unsigned char *)(((uintptr_t)_temp_buffer + 0xffff) & ~0xffff);
   }
 
   return _safe_buffer_start;

+ 2 - 2
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -4936,7 +4936,7 @@ draw_primitive_up(D3DPRIMITIVETYPE primitive_type,
     _d3d_device->DrawPrimitiveUP(primitive_type, primitive_count,
          buffer_start, stride);
 
-  } else if ((((long)buffer_end ^ (long)buffer_start) & ~0xffff) == 0) {
+  } else if ((((uintptr_t)buffer_end ^ (uintptr_t)buffer_start) & ~0xffff) == 0) {
     // No problem; we can draw the buffer directly.
     _d3d_device->DrawPrimitiveUP(primitive_type, primitive_count,
          buffer_start, stride);
@@ -4978,7 +4978,7 @@ draw_indexed_primitive_up(D3DPRIMITIVETYPE primitive_type,
       (primitive_type, min_index, max_index - min_index + 1, num_primitives,
        index_data, index_type, buffer, stride);
 
-  } else if ((((long)buffer_end ^ (long)buffer_start) & ~0xffff) == 0) {
+  } else if ((((uintptr_t)buffer_end ^ (uintptr_t)buffer_start) & ~0xffff) == 0) {
     // No problem; we can draw the buffer directly.
     _d3d_device->DrawIndexedPrimitiveUP
       (primitive_type, min_index, max_index - min_index + 1, num_primitives,

+ 1 - 1
panda/src/dxgsg9/dxTextureContext9.cxx

@@ -680,7 +680,7 @@ create_texture(DXScreenData &scrn) {
     << "NumColorChannels: " << num_color_channels << "; NumAlphaBits: "
     << num_alpha_bits << "; targetbpp: " <<target_bpp
     << "; _supported_tex_formats_mask: 0x"
-    << (void*)scrn._supported_tex_formats_mask
+    << hex << scrn._supported_tex_formats_mask << dec
     << "; NeedLuminance: " << needs_luminance << endl;
   goto error_exit;
 

+ 4 - 4
panda/src/dxgsg9/wdxGraphicsWindow9.cxx

@@ -880,10 +880,10 @@ choose_device() {
       << ", Driver: " << adapter_info.Driver << ", DriverVersion: ("
       << HIWORD(DrvVer->HighPart) << "." << LOWORD(DrvVer->HighPart) << "."
       << HIWORD(DrvVer->LowPart) << "." << LOWORD(DrvVer->LowPart)
-      << ")\nVendorID: 0x" << (void*) adapter_info.VendorId
-      << " DeviceID: 0x" <<  (void*) adapter_info.DeviceId
-      << " SubsysID: 0x" << (void*) adapter_info.SubSysId
-      << " Revision: 0x" << (void*) adapter_info.Revision << endl;
+      << ")\nVendorID: 0x" << hex << adapter_info.VendorId
+      << " DeviceID: 0x" << adapter_info.DeviceId
+      << " SubsysID: 0x" << adapter_info.SubSysId
+      << " Revision: 0x" << adapter_info.Revision << dec << endl;
 
     HMONITOR _monitor = dxpipe->__d3d9->GetAdapterMonitor(i);
     if (_monitor == NULL) {

+ 98 - 2
panda/src/event/asyncFuture.I

@@ -17,7 +17,6 @@
 INLINE AsyncFuture::
 AsyncFuture() :
   _manager(nullptr),
-  _cvar(nullptr),
   _future_state(FS_pending),
   _result(nullptr) {
 }
@@ -28,7 +27,7 @@ AsyncFuture() :
  */
 INLINE bool AsyncFuture::
 done() const {
-  return (FutureState)AtomicAdjust::get(_future_state) != FS_pending;
+  return (FutureState)AtomicAdjust::get(_future_state) >= FS_finished;
 }
 
 /**
@@ -46,6 +45,7 @@ cancelled() const {
  */
 INLINE void AsyncFuture::
 set_done_event(const string &done_event) {
+  nassertv(!done());
   _done_event = done_event;
 }
 
@@ -107,3 +107,99 @@ INLINE void AsyncFuture::
 set_result(TypedWritableReferenceCount *result) {
   set_result(result, result);
 }
+
+INLINE void AsyncFuture::
+set_result(const EventParameter &result) {
+  set_result(result.get_ptr(), result.get_ptr());
+}
+
+/**
+ * Creates a new future that returns `done()` when all of the contained
+ * futures are done.
+ *
+ * Calling `cancel()` on the returned future will result in all contained
+ * futures that have not yet finished to be cancelled.
+ */
+INLINE AsyncFuture *AsyncFuture::
+gather(Futures futures) {
+  if (futures.empty()) {
+    AsyncFuture *fut = new AsyncFuture;
+    fut->_future_state = (AtomicAdjust::Integer)FS_finished;
+    return fut;
+  } else if (futures.size() == 1) {
+    return futures[0].p();
+  } else {
+    return (AsyncFuture *)new AsyncGatheringFuture(move(futures));
+  }
+}
+
+/**
+ * Tries to atomically lock the future, assuming it is pending.  Returns false
+ * if it is not in the pending state, implying it's either done or about to be
+ * cancelled.
+ */
+INLINE bool AsyncFuture::
+try_lock_pending() {
+  return set_future_state(FS_locked_pending);
+}
+
+/**
+ * Should be called after try_lock_pending() returns true.
+ */
+INLINE void AsyncFuture::
+unlock(FutureState new_state) {
+  nassertv(new_state != FS_locked_pending);
+  FutureState orig_state = (FutureState)AtomicAdjust::set(_future_state, (AtomicAdjust::Integer)new_state);
+  nassertv(orig_state == FS_locked_pending);
+}
+
+/**
+ * Atomically changes the future state from pending to another state.  Returns
+ * true if successful, false if the future was already done.
+ * Note that once a future is in a "done" state (ie. cancelled or finished) it
+ * can never change state again.
+ */
+INLINE bool AsyncFuture::
+set_future_state(FutureState state) {
+  FutureState orig_state = (FutureState)
+    AtomicAdjust::compare_and_exchange(
+      _future_state,
+      (AtomicAdjust::Integer)FS_pending,
+      (AtomicAdjust::Integer)state);
+
+  while (orig_state == FS_locked_pending) {
+    Thread::force_yield();
+    orig_state = (FutureState)AtomicAdjust::compare_and_exchange(
+      _future_state,
+      (AtomicAdjust::Integer)FS_pending,
+      (AtomicAdjust::Integer)state);
+  }
+
+  return orig_state == FS_pending;
+}
+
+/**
+ * Returns the number of futures that were passed to the constructor.
+ */
+INLINE size_t AsyncGatheringFuture::
+get_num_futures() const {
+  return _futures.size();
+}
+
+/**
+ * Returns the nth future that was passed into the constructor.
+ */
+INLINE AsyncFuture *AsyncGatheringFuture::
+get_future(size_t i) const {
+  nassertr(i < _futures.size(), nullptr);
+  return _futures[i].p();
+}
+
+/**
+ * Returns the result of the nth future that was passed into the constructor.
+ */
+INLINE TypedObject *AsyncGatheringFuture::
+get_result(size_t i) const {
+  nassertr(i < _futures.size(), nullptr);
+  return _futures[i]->get_result();
+}

+ 265 - 84
panda/src/event/asyncFuture.cxx

@@ -20,31 +20,33 @@
 #include "throw_event.h"
 
 TypeHandle AsyncFuture::_type_handle;
+TypeHandle AsyncGatheringFuture::_type_handle;
 
 /**
  * Destroys the future.  Assumes notify_done() has already been called.
  */
 AsyncFuture::
 ~AsyncFuture() {
-  delete _cvar;
-  nassertv(_waiting_tasks.empty());
+  // If this triggers, the future destroyed before it was cancelled, which is
+  // not valid.  Unless we should simply call cancel() here?
+  nassertv(_waiting.empty());
 }
 
 /**
  * Cancels the future.  Returns true if it was cancelled, or false if the
- * future was already done.
+ * future was already done.  Either way, done() will return true after this
+ * call returns.
+ *
  * In the case of a task, this is equivalent to remove().
  */
 bool AsyncFuture::
 cancel() {
-  if (!done()) {
-    if (_manager == nullptr) {
-      _manager = AsyncTaskManager::get_global_ptr();
-    }
-    MutexHolder holder(_manager->_lock);
+  if (set_future_state(FS_cancelled)) {
+    // The compare-swap operation succeeded, so schedule the callbacks.
     notify_done(false);
     return true;
   } else {
+    // It's already done.
     return false;
   }
 }
@@ -55,6 +57,22 @@ cancel() {
 void AsyncFuture::
 output(ostream &out) const {
   out << get_type();
+  FutureState state = (FutureState)AtomicAdjust::get(_future_state);
+  switch (state) {
+  case FS_pending:
+  case FS_locked_pending:
+    out << " (pending)";
+    break;
+  case FS_finished:
+    out << " (finished)";
+    break;
+  case FS_cancelled:
+    out << " (cancelled)";
+    break;
+  default:
+    out << " (**INVALID**)";
+    break;
+  }
 }
 
 /**
@@ -66,25 +84,18 @@ wait() {
     return;
   }
 
-  // If we don't have a manager, use the global one.
-  if (_manager == nullptr) {
-    _manager = AsyncTaskManager::get_global_ptr();
+  PStatTimer timer(AsyncTaskChain::_wait_pcollector);
+  if (task_cat.is_debug()) {
+    task_cat.debug()
+      << "Waiting for future " << *this << "\n";
   }
 
-  MutexHolder holder(_manager->_lock);
-  if (!done()) {
-    if (_cvar == nullptr) {
-      _cvar = new ConditionVarFull(_manager->_lock);
-    }
-    if (task_cat.is_debug()) {
-      task_cat.debug()
-        << "Waiting for future " << *this << "\n";
-    }
-    PStatTimer timer(AsyncTaskChain::_wait_pcollector);
-    while (!done()) {
-      _cvar->wait();
-    }
-  }
+  // Continue to yield while the future isn't done.  It may be more efficient
+  // to use a condition variable, but let's not add the extra complexity
+  // unless we're sure that we need it.
+  do {
+    Thread::force_yield();
+  } while (!done());
 }
 
 /**
@@ -96,46 +107,52 @@ wait(double timeout) {
     return;
   }
 
-  // If we don't have a manager, use the global one.
-  if (_manager == nullptr) {
-    _manager = AsyncTaskManager::get_global_ptr();
+  PStatTimer timer(AsyncTaskChain::_wait_pcollector);
+  if (task_cat.is_debug()) {
+    task_cat.debug()
+      << "Waiting up to " << timeout << " seconds for future " << *this << "\n";
   }
 
-  MutexHolder holder(_manager->_lock);
-  if (!done()) {
-    if (_cvar == nullptr) {
-      _cvar = new ConditionVarFull(_manager->_lock);
-    }
-    if (task_cat.is_debug()) {
-      task_cat.debug()
-        << "Waiting up to " << timeout << " seconds for future " << *this << "\n";
-    }
-    PStatTimer timer(AsyncTaskChain::_wait_pcollector);
-    _cvar->wait(timeout);
-  }
+  // Continue to yield while the future isn't done.  It may be more efficient
+  // to use a condition variable, but let's not add the extra complexity
+  // unless we're sure that we need it.
+  ClockObject *clock = ClockObject::get_global_clock();
+  double end = clock->get_real_time() + timeout;
+  do {
+    Thread::force_yield();
+  } while (!done() && clock->get_real_time() < end);
 }
 
 /**
- * Schedules the done callbacks.  Assumes the manager's lock is held, and that
- * the future is currently in the 'pending' state.
+ * Schedules the done callbacks.  Called after the future has just entered the
+ * 'done' state.
  * @param clean_exit true if finished successfully, false if cancelled.
  */
 void AsyncFuture::
 notify_done(bool clean_exit) {
-  nassertv(_manager != nullptr);
-  nassertv(_manager->_lock.debug_is_locked());
-  nassertv(_future_state == FS_pending);
-
-  pvector<AsyncTask *>::iterator it;
-  for (it = _waiting_tasks.begin(); it != _waiting_tasks.end(); ++it) {
-    AsyncTask *task = *it;
-    nassertd(task->_manager == _manager) continue;
-    task->_state = AsyncTask::S_active;
-    task->_chain->_active.push_back(task);
-    --task->_chain->_num_awaiting_tasks;
-    unref_delete(task);
+  nassertv(done());
+
+  // This will only be called by the thread that managed to set the
+  // _future_state away from the "pending" state, so this is thread safe.
+
+  Futures::iterator it;
+  for (it = _waiting.begin(); it != _waiting.end(); ++it) {
+    AsyncFuture *fut = *it;
+    if (fut->is_task()) {
+      // It's a task.  Make it active again.
+      wake_task((AsyncTask *)fut);
+    } else {
+      // It's a gathering future.  Decrease the pending count on it, and if
+      // we're the last one, call notify_done() on it.
+      AsyncGatheringFuture *gather = (AsyncGatheringFuture *)fut;
+      if (!AtomicAdjust::dec(gather->_num_pending)) {
+        if (gather->set_future_state(FS_finished)) {
+          gather->notify_done(true);
+        }
+      }
+    }
   }
-  _waiting_tasks.clear();
+  _waiting.clear();
 
   // For historical reasons, we don't send the "done event" if the future was
   // cancelled.
@@ -144,17 +161,6 @@ notify_done(bool clean_exit) {
     event->add_parameter(EventParameter(this));
     throw_event(move(event));
   }
-
-  nassertv_always(FS_pending ==
-    (FutureState)AtomicAdjust::compare_and_exchange(
-      _future_state,
-      (AtomicAdjust::Integer)FS_pending,
-      (AtomicAdjust::Integer)(clean_exit ? FS_finished : FS_cancelled)));
-
-  // Finally, notify any threads that may be waiting.
-  if (_cvar != nullptr) {
-    _cvar->notify_all();
-  }
 }
 
 /**
@@ -168,28 +174,203 @@ notify_done(bool clean_exit) {
  */
 void AsyncFuture::
 set_result(TypedObject *ptr, ReferenceCount *ref_ptr) {
-  nassertv(!done());
-  // If we don't have a manager, use the global one.
-  if (_manager == nullptr) {
-    _manager = AsyncTaskManager::get_global_ptr();
-  }
-  MutexHolder holder(_manager->_lock);
-  _result = ptr;
-  _result_ref = ref_ptr;
-  notify_done(true);
+  // We don't strictly need to lock the future since only one thread is
+  // allowed to call set_result(), but we might as well.
+  FutureState orig_state = (FutureState)AtomicAdjust::
+    compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
+                                        (AtomicAdjust::Integer)FS_locked_pending);
+
+  while (orig_state == FS_locked_pending) {
+    Thread::force_yield();
+    orig_state = (FutureState)AtomicAdjust::
+      compare_and_exchange(_future_state, (AtomicAdjust::Integer)FS_pending,
+                                          (AtomicAdjust::Integer)FS_locked_pending);
+  }
+
+  if (orig_state == FS_pending) {
+    _result = ptr;
+    _result_ref = ref_ptr;
+    unlock(FS_finished);
+
+    // OK, now our thread owns the _waiting vector et al.
+    notify_done(true);
+
+  } else if (orig_state == FS_cancelled) {
+    // This was originally illegal, but there is a chance that the future was
+    // cancelled while another thread was setting the result.  So, we drop
+    // this, but we can issue a warning.
+    task_cat.warning()
+      << "Ignoring set_result() called on cancelled " << *this << "\n";
+
+  } else {
+    task_cat.error()
+      << "set_result() was called on finished " << *this << "\n";
+  }
 }
 
 /**
  * Indicates that the given task is waiting for this future to complete.  When
- * the future is done, it will reactivate the given task.
- * Assumes the manager's lock is held.
+ * the future is done, it will reactivate the given task.  If this is called
+ * while the future is already done, schedules the task immediately.
+ * Assumes the manager's lock is not held.
+ * @returns true if the future was pending, false if it was already done.
  */
-void AsyncFuture::
+bool AsyncFuture::
 add_waiting_task(AsyncTask *task) {
-  nassertv(!done());
-  nassertv(_manager != nullptr);
-  nassertv(_manager->_lock.debug_is_locked());
-  task->ref();
-  _waiting_tasks.push_back(task);
-  nassertv(task->_manager == _manager);
+  nassertr(task->is_runnable(), false);
+
+  // We have to make sure we're not going to change state while we're in the
+  // process of adding the task.
+  if (try_lock_pending()) {
+    if (_manager == nullptr) {
+      _manager = task->_manager;
+    }
+
+    _waiting.push_back(task);
+
+    // Unlock the state.
+    unlock();
+    nassertr(task->_manager == nullptr || task->_manager == _manager, true);
+    return true;
+  } else {
+    // It's already done.  Wake the task immediately.
+    wake_task(task);
+    return false;
+  }
+}
+
+/**
+ * Reactivates the given task.  Assumes the manager lock is not held.
+ */
+void AsyncFuture::
+wake_task(AsyncTask *task) {
+  cerr << "waking task\n";
+  AsyncTaskManager *manager = task->_manager;
+  if (manager == nullptr) {
+    // If it's an unscheduled task, schedule it on the same manager as the
+    // rest of the waiting tasks.
+    manager = _manager;
+    if (manager == nullptr) {
+      manager = AsyncTaskManager::get_global_ptr();
+    }
+  }
+
+  MutexHolder holder(manager->_lock);
+  switch (task->_state) {
+  case AsyncTask::S_servicing_removed:
+    nassertv(task->_manager == _manager);
+    // Re-adding a self-removed task; this just means clearing the removed
+    // flag.
+    task->_state = AsyncTask::S_servicing;
+    return;
+
+  case AsyncTask::S_inactive:
+    // Schedule it immediately.
+    nassertv(task->_manager == nullptr);
+
+    if (task_cat.is_debug()) {
+      task_cat.debug()
+        << "Adding " << *task << " (woken by future " << *this << ")\n";
+    }
+
+    {
+      manager->_lock.release();
+      task->upon_birth(manager);
+      manager->_lock.acquire();
+      nassertv(task->_manager == nullptr &&
+               task->_state == AsyncTask::S_inactive);
+
+      AsyncTaskChain *chain = manager->do_find_task_chain(task->_chain_name);
+      if (chain == nullptr) {
+        task_cat.warning()
+          << "Creating implicit AsyncTaskChain " << task->_chain_name
+          << " for " << manager->get_type() << " " << manager->get_name() << "\n";
+        chain = manager->do_make_task_chain(task->_chain_name);
+      }
+      chain->do_add(task);
+    }
+    return;
+
+  case AsyncTask::S_awaiting:
+    nassertv(task->_manager == _manager);
+    task->_state = AsyncTask::S_active;
+    task->_chain->_active.push_back(task);
+    --task->_chain->_num_awaiting_tasks;
+    return;
+
+  default:
+    nassertv(false);
+    return;
+  }
+}
+
+/**
+ * @see AsyncFuture::gather
+ */
+AsyncGatheringFuture::
+AsyncGatheringFuture(AsyncFuture::Futures futures) :
+  _futures(move(futures)),
+  _num_pending(0) {
+
+  bool any_pending = false;
+
+  AsyncFuture::Futures::const_iterator it;
+  for (it = _futures.begin(); it != _futures.end(); ++it) {
+    AsyncFuture *fut = *it;
+    // If this returns true, the future is not yet done and we need to
+    // register ourselves with it.  This creates a circular reference, but it
+    // is resolved when the future is completed or cancelled.
+    if (fut->try_lock_pending()) {
+      if (_manager == nullptr) {
+        _manager = fut->_manager;
+      }
+      fut->_waiting.push_back((AsyncFuture *)this);
+      AtomicAdjust::inc(_num_pending);
+      fut->unlock();
+      any_pending = true;
+    }
+  }
+  if (!any_pending) {
+    // Start in the done state if all the futures we were passed are done.
+    // Note that it is only safe to set this member in this manner if indeed
+    // no other future holds a reference to us.
+    _future_state = (AtomicAdjust::Integer)FS_finished;
+  }
+}
+
+/**
+ * Cancels all the futures.  Returns true if any futures were cancelled.
+ * Makes sure that all the futures finish before this one is marked done, in
+ * order to maintain the guarantee that calling result() is safe when done()
+ * returns true.
+ */
+bool AsyncGatheringFuture::
+cancel() {
+  if (!done()) {
+    // Temporarily increase the pending count so that the notify_done()
+    // callbacks won't end up causing it to be set to "finished".
+    AtomicAdjust::inc(_num_pending);
+
+    bool any_cancelled = false;
+    AsyncFuture::Futures::const_iterator it;
+    for (it = _futures.begin(); it != _futures.end(); ++it) {
+      AsyncFuture *fut = *it;
+      if (fut->cancel()) {
+        any_cancelled = true;
+      }
+    }
+
+    // Now change state to "cancelled" and call the notify_done() callbacks.
+    // Don't call notify_done() if another thread has beaten us to it.
+    if (set_future_state(FS_cancelled)) {
+      notify_done(false);
+    }
+
+    // Decreasing the pending count is kind of pointless now, so we do it only
+    // in a debug build.
+    nassertr(!AtomicAdjust::dec(_num_pending), any_cancelled);
+    return any_cancelled;
+  } else {
+    return false;
+  }
 }

+ 85 - 20
panda/src/event/asyncFuture.h

@@ -17,7 +17,7 @@
 #include "pandabase.h"
 #include "typedReferenceCount.h"
 #include "typedWritableReferenceCount.h"
-#include "conditionVar.h"
+#include "eventParameter.h"
 #include "atomicAdjust.h"
 
 class AsyncTaskManager;
@@ -30,21 +30,29 @@ class ConditionVarFull;
  * as well as register callbacks for this future's completion.
  *
  * An AsyncFuture can be awaited from within a coroutine or task.  It keeps
- * track of a list of tasks waiting for this future so that they are
- * automatically reactivated upon this future's completion.
+ * track of tasks waiting for this future and automatically reactivates them
+ * upon this future's completion.
  *
  * A task itself is also a subclass of AsyncFuture.  Other subclasses are
- * possible, although subclassing is not necessary for most purposes.
+ * not generally necessary, except to override the function of `cancel()`.
  *
- * The `done()` method is used to check whether the future has completed and
- * a result is available (whether it is cancelled or finished).
+ * Until the future is done, it is "owned" by the resolver thread, though it's
+ * still legal for other threads to query its state.  When the resolver thread
+ * resolves this future using `set_result()`, or any thread calls `cancel()`,
+ * it instantly enters the "done" state, after which the result becomes a
+ * read-only field that all threads can access.
  *
- * To get the result of the future in C++, use `wait()` and `get_result()`.
- * In Python, the functionality of both of those calls is wrapped into the
- * `result()` method, which waits for the future to complete before either
- * returning the result or throwing an exception if the future was cancelled.
+ * When the future returns true for done(), a thread can use cancelled() to
+ * determine whether the future was cancelled or get_result() to access the
+ * result of the operation.  Not all operations define a meaningful result
+ * value, so some will always return nullptr.
+ *
+ * In Python, the `cancelled()`, `wait()` and `get_result()` methods are
+ * wrapped up into a single `result()` method which waits for the future to
+ * complete before either returning the result or throwing an exception if the
+ * future was cancelled.
  * However, it is preferable to use the `await` keyword when running from a
- * coroutine.
+ * coroutine, which only suspends the current task and not the entire thread.
  *
  * This API aims to mirror and be compatible with Python's Future class.
  */
@@ -58,7 +66,7 @@ PUBLISHED:
 
   INLINE bool done() const;
   INLINE bool cancelled() const;
-  EXTENSION(PyObject *result(double timeout = -1.0) const);
+  EXTENSION(PyObject *result(PyObject *timeout = Py_None) const);
 
   virtual bool cancel();
 
@@ -66,45 +74,64 @@ PUBLISHED:
   INLINE const string &get_done_event() const;
   MAKE_PROPERTY(done_event, get_done_event, set_done_event);
 
+  EXTENSION(PyObject *add_done_callback(PyObject *self, PyObject *fn));
+
+  EXTENSION(static PyObject *gather(PyObject *args));
+
   virtual void output(ostream &out) const;
 
-  void wait();
-  void wait(double timeout);
+  BLOCKING void wait();
+  BLOCKING void wait(double timeout);
 
   INLINE void set_result(nullptr_t);
   INLINE void set_result(TypedObject *result);
   INLINE void set_result(TypedReferenceCount *result);
   INLINE void set_result(TypedWritableReferenceCount *result);
+  INLINE void set_result(const EventParameter &result);
 
 public:
+  void set_result(TypedObject *ptr, ReferenceCount *ref_ptr);
+
   INLINE TypedObject *get_result() const;
   INLINE void get_result(TypedObject *&ptr, ReferenceCount *&ref_ptr) const;
 
+  typedef pvector<PT(AsyncFuture)> Futures;
+  INLINE static AsyncFuture *gather(Futures futures);
+
+  virtual bool is_task() const {return false;}
+
   void notify_done(bool clean_exit);
+  bool add_waiting_task(AsyncTask *task);
 
 private:
-  void set_result(TypedObject *ptr, ReferenceCount *ref_ptr);
-  void add_waiting_task(AsyncTask *task);
+  void wake_task(AsyncTask *task);
 
 protected:
   enum FutureState {
+    // Pending states
     FS_pending,
+    FS_locked_pending,
+
+    // Done states
     FS_finished,
     FS_cancelled,
   };
+  INLINE bool try_lock_pending();
+  INLINE void unlock(FutureState new_state = FS_pending);
+  INLINE bool set_future_state(FutureState state);
 
   AsyncTaskManager *_manager;
-  ConditionVarFull *_cvar;
   TypedObject *_result;
   PT(ReferenceCount) _result_ref;
   AtomicAdjust::Integer _future_state;
 
   string _done_event;
 
-  // Tasks waiting for this one to complete.  These are reference counted, but
-  // we can't store them in a PointerTo for circular dependency reasons.
-  pvector<AsyncTask *> _waiting_tasks;
+  // Tasks and gathering futures waiting for this one to complete.
+  Futures _waiting;
 
+  friend class AsyncGatheringFuture;
+  friend class AsyncTaskChain;
   friend class PythonTask;
 
 public:
@@ -130,6 +157,44 @@ INLINE ostream &operator << (ostream &out, const AsyncFuture &fut) {
   return out;
 };
 
+/**
+ * Specific future that collects the results of several futures.
+ */
+class EXPCL_PANDA_EVENT AsyncGatheringFuture FINAL : public AsyncFuture {
+private:
+  AsyncGatheringFuture(AsyncFuture::Futures futures);
+
+public:
+  virtual bool cancel() override;
+
+  INLINE size_t get_num_futures() const;
+  INLINE AsyncFuture *get_future(size_t i) const;
+  INLINE TypedObject *get_result(size_t i) const;
+
+private:
+  const Futures _futures;
+  AtomicAdjust::Integer _num_pending;
+
+  friend class AsyncFuture;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncFuture::init_type();
+    register_type(_type_handle, "AsyncGatheringFuture",
+                  AsyncFuture::get_class_type());
+  }
+  virtual TypeHandle get_type() const override {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() override {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
 #include "asyncFuture.I"
 
 #endif

+ 152 - 9
panda/src/event/asyncFuture_ext.cxx

@@ -12,12 +12,16 @@
  */
 
 #include "asyncFuture_ext.h"
+#include "asyncTaskSequence.h"
+#include "eventParameter.h"
+#include "paramValue.h"
 #include "pythonTask.h"
 
 #ifdef HAVE_PYTHON
 
 #ifndef CPPPARSER
 extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
+extern struct Dtool_PyTypedObject Dtool_ParamValueBase;
 extern struct Dtool_PyTypedObject Dtool_TypedObject;
 #endif
 
@@ -32,7 +36,45 @@ static PyObject *get_done_result(const AsyncFuture *future) {
       // any PyObject value or raise an exception.
       const PythonTask *task = (const PythonTask *)future;
       return task->get_result();
+
+    } else if (future->is_of_type(AsyncTaskSequence::get_class_type())) {
+      // If it's an AsyncTaskSequence, get the result for each task.
+      const AsyncTaskSequence *task = (const AsyncTaskSequence *)future;
+      Py_ssize_t num_tasks = (Py_ssize_t)task->get_num_tasks();
+      PyObject *results = PyTuple_New(num_tasks);
+
+      for (Py_ssize_t i = 0; i < num_tasks; ++i) {
+        PyObject *result = get_done_result(task->get_task(i));
+        if (result != nullptr) {
+          // This steals a reference.
+          PyTuple_SET_ITEM(results, i, result);
+        } else {
+          Py_DECREF(results);
+          return nullptr;
+        }
+      }
+      return results;
+
+    } else if (future->is_of_type(AsyncGatheringFuture::get_class_type())) {
+      // If it's an AsyncGatheringFuture, get the result for each future.
+      const AsyncGatheringFuture *gather = (const AsyncGatheringFuture *)future;
+      Py_ssize_t num_futures = (Py_ssize_t)gather->get_num_futures();
+      PyObject *results = PyTuple_New(num_futures);
+
+      for (Py_ssize_t i = 0; i < num_futures; ++i) {
+        PyObject *result = get_done_result(gather->get_future((size_t)i));
+        if (result != nullptr) {
+          // This steals a reference.
+          PyTuple_SET_ITEM(results, i, result);
+        } else {
+          Py_DECREF(results);
+          return nullptr;
+        }
+      }
+      return results;
+
     } else {
+      // It's any other future.
       ReferenceCount *ref_ptr;
       TypedObject *ptr;
       future->get_result(ptr, ref_ptr);
@@ -42,13 +84,36 @@ static PyObject *get_done_result(const AsyncFuture *future) {
         return Py_None;
       }
 
+      TypeHandle type = ptr->get_type();
+      if (type.is_derived_from(ParamValueBase::get_class_type())) {
+        // If this is a ParamValueBase, return the 'value' property.
+        // EventStoreInt and Double are not exposed to Python for some reason.
+        if (type == EventStoreInt::get_class_type()) {
+          return Dtool_WrapValue(((EventStoreInt *)ptr)->get_value());
+        } else if (type == EventStoreDouble::get_class_type()) {
+          return Dtool_WrapValue(((EventStoreDouble *)ptr)->get_value());
+        }
+
+        ParamValueBase *value = (ParamValueBase *)ptr;
+        PyObject *wrap = DTool_CreatePyInstanceTyped
+          ((void *)value, Dtool_ParamValueBase, false, false, type.get_index());
+        if (wrap != nullptr) {
+          PyObject *value = PyObject_GetAttrString(wrap, "value");
+          if (value != nullptr) {
+            return value;
+          }
+          PyErr_Restore(nullptr, nullptr, nullptr);
+          Py_DECREF(wrap);
+        }
+      }
+
       if (ref_ptr != nullptr) {
         ref_ptr->ref();
       }
 
       return DTool_CreatePyInstanceTyped
         ((void *)ptr, Dtool_TypedObject, (ref_ptr != nullptr), false,
-         ptr->get_type_index());
+         type.get_index());
     }
   } else {
     // If the future was cancelled, we should raise an exception.
@@ -62,8 +127,8 @@ static PyObject *get_done_result(const AsyncFuture *future) {
       }
       // If we can't get that, we should pretend and make our own.
       if (exc_type == nullptr) {
-        exc_type = PyErr_NewExceptionWithDoc("concurrent.futures._base.CancelledError",
-                                             "The Future was cancelled.",
+        exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.CancelledError",
+                                             (char*)"The Future was cancelled.",
                                              nullptr, nullptr);
       }
     }
@@ -121,7 +186,7 @@ __await__(PyObject *self) {
  * raises TimeoutError.
  */
 PyObject *Extension<AsyncFuture>::
-result(double timeout) const {
+result(PyObject *timeout) const {
   if (!_this->done()) {
     // Not yet done?  Wait until it is done, or until a timeout occurs.  But
     // first check to make sure we're not trying to deadlock the thread.
@@ -136,11 +201,15 @@ result(double timeout) const {
     PyThreadState *_save;
     Py_UNBLOCK_THREADS
 #endif
-    //TODO: check why gcc and clang don't like infinity timeout.
-    if (cinf(timeout) || timeout < 0.0) {
+    if (timeout == Py_None) {
       _this->wait();
     } else {
-      _this->wait(timeout);
+      PyObject *num = PyNumber_Float(timeout);
+      if (num != nullptr) {
+        _this->wait(PyFloat_AS_DOUBLE(num));
+      } else {
+        return Dtool_Raise_ArgTypeError(timeout, 0, "result", "float");
+      }
     }
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
     Py_BLOCK_THREADS
@@ -158,8 +227,8 @@ result(double timeout) const {
         }
         // If we can't get that, we should pretend and make our own.
         if (exc_type == nullptr) {
-          exc_type = PyErr_NewExceptionWithDoc("concurrent.futures._base.TimeoutError",
-                                               "The operation exceeded the given deadline.",
+          exc_type = PyErr_NewExceptionWithDoc((char*)"concurrent.futures._base.TimeoutError",
+                                               (char*)"The operation exceeded the given deadline.",
                                                nullptr, nullptr);
         }
       }
@@ -172,4 +241,78 @@ result(double timeout) const {
   return get_done_result(_this);
 }
 
+/**
+ * Schedules the given function to be run as soon as the future is complete.
+ * This is also called if the future is cancelled.
+ * If the future is already done, the callback is scheduled right away.
+ */
+PyObject *Extension<AsyncFuture>::
+add_done_callback(PyObject *self, PyObject *fn) {
+  if (!PyCallable_Check(fn)) {
+    return Dtool_Raise_ArgTypeError(fn, 0, "add_done_callback", "callable");
+  }
+
+  PythonTask *task = new PythonTask(fn);
+  Py_DECREF(task->_args);
+  task->_args = PyTuple_Pack(1, self);
+  task->_append_task = false;
+  task->_ignore_return = true;
+
+  // If this is an AsyncTask, make sure it is scheduled on the same chain.
+  if (_this->is_task()) {
+    AsyncTask *this_task = (AsyncTask *)_this;
+    task->set_task_chain(this_task->get_task_chain());
+  }
+
+  _this->add_waiting_task(task);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Creates a new future that returns `done()` when all of the contained
+ * futures are done.
+ *
+ * Calling `cancel()` on the returned future will result in all contained
+ * futures that have not yet finished to be cancelled.
+ */
+PyObject *Extension<AsyncFuture>::
+gather(PyObject *args) {
+  if (!PyTuple_Check(args)) {
+    return Dtool_Raise_TypeError("args is not a tuple");
+  }
+
+  Py_ssize_t size = Py_SIZE(args);
+  AsyncFuture::Futures futures;
+  futures.reserve(size);
+
+  for (Py_ssize_t i = 0; i < size; ++i) {
+    PyObject *item = PyTuple_GET_ITEM(args, i);
+    if (DtoolInstance_Check(item)) {
+      AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(item, Dtool_AsyncFuture);
+      if (fut != nullptr) {
+        futures.push_back(fut);
+        continue;
+      }
+#if PY_VERSION_HEX >= 0x03050000
+    } else if (PyCoro_CheckExact(item)) {
+      // We allow passing in a coroutine instead of a future.  This causes it
+      // to be scheduled as a task.
+      futures.push_back(new PythonTask(item));
+      continue;
+#endif
+    }
+    return Dtool_Raise_ArgTypeError(item, i, "gather", "coroutine, task or future");
+  }
+
+  AsyncFuture *future = AsyncFuture::gather(move(futures));
+  if (future != nullptr) {
+    future->ref();
+    return DTool_CreatePyInstanceTyped((void *)future, Dtool_AsyncFuture, true, false, future->get_type_index());
+  } else {
+    return PyErr_NoMemory();
+  }
+}
+
 #endif

+ 5 - 1
panda/src/event/asyncFuture_ext.h

@@ -29,7 +29,11 @@ public:
   static PyObject *__await__(PyObject *self);
   static PyObject *__iter__(PyObject *self) { return __await__(self); }
 
-  PyObject *result(double timeout = -1.0) const;
+  PyObject *result(PyObject *timeout = Py_None) const;
+
+  PyObject *add_done_callback(PyObject *self, PyObject *fn);
+
+  static PyObject *gather(PyObject *args);
 };
 
 #endif  // HAVE_PYTHON

+ 1 - 0
panda/src/event/asyncTask.h

@@ -104,6 +104,7 @@ protected:
   DoneStatus unlock_and_do_task();
 
   virtual bool cancel() FINAL;
+  virtual bool is_task() const FINAL {return true;}
 
   virtual bool is_runnable();
   virtual DoneStatus do_task();

+ 2 - 1
panda/src/event/asyncTaskChain.cxx

@@ -778,7 +778,8 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
 
   _manager->remove_task_by_name(task);
 
-  if (upon_death && !task->done()) {
+  if (upon_death && task->set_future_state(clean_exit ? AsyncFuture::FS_finished
+                                                      : AsyncFuture::FS_cancelled)) {
     task->notify_done(clean_exit);
   }
 

+ 1 - 0
panda/src/event/config_event.cxx

@@ -33,6 +33,7 @@ NotifyCategoryDef(task, "");
 
 ConfigureFn(config_event) {
   AsyncFuture::init_type();
+  AsyncGatheringFuture::init_type();
   AsyncTask::init_type();
   AsyncTaskChain::init_type();
   AsyncTaskManager::init_type();

+ 17 - 22
panda/src/event/pythonTask.cxx

@@ -45,6 +45,7 @@ PythonTask(PyObject *func_or_coro, const string &name) :
   _exc_traceback(nullptr),
   _generator(nullptr),
   _future_done(nullptr),
+  _ignore_return(false),
   _retrieved_exception(false) {
 
   nassertv(func_or_coro != nullptr);
@@ -169,9 +170,7 @@ get_args() {
     }
 
     this->ref();
-    PyObject *self =
-      DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
-                                  true, false, get_type_index());
+    PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
     PyTuple_SET_ITEM(with_task, num_args, self);
     return with_task;
 
@@ -588,20 +587,21 @@ do_python_task() {
       AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
       if (fut != nullptr) {
         // Suspend execution of this task until this other task has completed.
-        AsyncTaskManager *manager = fut->_manager;
-        if (manager == nullptr) {
-          manager = _manager;
-          fut->_manager = manager;
-        }
-        nassertr(manager == _manager, DS_interrupt);
-        MutexHolder holder(manager->_lock);
-        if (fut != (AsyncFuture *)this) {
-          if (!fut->done()) {
+        if (fut != (AsyncFuture *)this && !fut->done()) {
+          if (fut->is_task()) {
+            // This is actually a task, do we need to schedule it with the
+            // manager?  This allows doing something like
+            //   await Task.pause(1.0)
+            // directly instead of having to do:
+            //   await taskMgr.add(Task.pause(1.0))
+            AsyncTask *task = (AsyncTask *)fut;
+            _manager->add(task);
+          }
+          if (fut->add_waiting_task(this)) {
             if (task_cat.is_debug()) {
               task_cat.debug()
                 << *this << " is now awaiting <" << *fut << ">.\n";
             }
-            fut->add_waiting_task(this);
           } else {
             // The task is already done.  Continue at next opportunity.
             if (task_cat.is_debug()) {
@@ -664,7 +664,7 @@ do_python_task() {
     return DS_interrupt;
   }
 
-  if (result == Py_None) {
+  if (result == Py_None || _ignore_return) {
     Py_DECREF(result);
     return DS_done;
   }
@@ -861,15 +861,10 @@ void PythonTask::
 call_function(PyObject *function) {
   if (function != Py_None) {
     this->ref();
-    PyObject *self =
-      DTool_CreatePyInstanceTyped(this, Dtool_TypedReferenceCount,
-                                  true, false, get_type_index());
-    PyObject *args = Py_BuildValue("(O)", self);
-    Py_DECREF(self);
-
-    PyObject *result = PyObject_CallObject(function, args);
+    PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
+    PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
     Py_XDECREF(result);
-    Py_DECREF(args);
+    Py_DECREF(self);
   }
 }
 

+ 2 - 1
panda/src/event/pythonTask.h

@@ -26,7 +26,7 @@
  * This class exists to allow association of a Python function or coroutine
  * with the AsyncTaskManager.
  */
-class PythonTask : public AsyncTask {
+class PythonTask FINAL : public AsyncTask {
 PUBLISHED:
   PythonTask(PyObject *function = Py_None, const string &name = string());
   virtual ~PythonTask();
@@ -123,6 +123,7 @@ private:
   PyObject *_future_done;
 
   bool _append_task;
+  bool _ignore_return;
   bool _registered_to_owner;
   mutable bool _retrieved_exception;
 

+ 26 - 0
panda/src/express/pointerToArray.I

@@ -581,6 +581,19 @@ node_unref() const {
   return ((To *)(this->_void_ptr))->node_unref();
 }
 
+/**
+ * Counts the frequency at which the given element occurs in the vector.
+ */
+template<class Element>
+INLINE size_t PointerToArray<Element>::
+count(const Element &value) const {
+  if ((this->_void_ptr) != nullptr) {
+    return std::count(begin(), end(), value);
+  } else {
+    return 0;
+  }
+}
+
 /**
  *
  */
@@ -1006,6 +1019,19 @@ node_unref() const {
   return ((To *)(this->_void_ptr))->node_unref();
 }
 
+/**
+ * Counts the frequency at which the given element occurs in the vector.
+ */
+template<class Element>
+INLINE size_t ConstPointerToArray<Element>::
+count(const Element &value) const {
+  if ((this->_void_ptr) != nullptr) {
+    return std::count(begin(), end(), value);
+  } else {
+    return 0;
+  }
+}
+
 /**
  *
  */

+ 8 - 0
panda/src/express/pointerToArray.h

@@ -112,6 +112,8 @@ PUBLISHED:
   INLINE int get_ref_count() const;
   INLINE int get_node_ref_count() const;
 
+  INLINE size_t count(const Element &) const;
+
 #ifdef HAVE_PYTHON
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
@@ -214,6 +216,8 @@ public:
   INLINE void node_ref() const;
   INLINE bool node_unref() const;
 
+  INLINE size_t count(const Element &) const;
+
   // Reassignment is by pointer, not memberwise as with a vector.
   INLINE PointerToArray<Element> &
   operator = (ReferenceCountedVector<Element> *ptr);
@@ -266,6 +270,8 @@ PUBLISHED:
   INLINE int get_ref_count() const;
   INLINE int get_node_ref_count() const;
 
+  INLINE size_t count(const Element &) const;
+
 #ifdef HAVE_PYTHON
   EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
   EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
@@ -341,6 +347,8 @@ PUBLISHED:
   INLINE void node_ref() const;
   INLINE bool node_unref() const;
 
+  INLINE size_t count(const Element &) const;
+
   // Reassignment is by pointer, not memberwise as with a vector.
   INLINE ConstPointerToArray<Element> &
   operator = (ReferenceCountedVector<Element> *ptr);

+ 20 - 17
panda/src/express/pointerToArray_ext.I

@@ -203,34 +203,37 @@ set_data(PyObject *data) {
     }
 
     PyBuffer_Release(&view);
-  } else {
-    Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
+    return;
   }
-#else
-  // In Python 2.5 we didn't have the new buffer protocol, only str.
-  if (PyString_CheckExact(data)) {
-    int size = PyString_Size(data);
-    if (size % sizeof(Element) != 0) {
+#endif
+
+  // In Python 2, there was also an older buffer protocol, supported by eg.
+  // str and array objects.
+#if PY_MAJOR_VERSION < 3
+  // The old, deprecated buffer interface, as used by eg. the array module.
+  const void *buffer;
+  Py_ssize_t buffer_len;
+  if (!PyUnicode_CheckExact(data) &&
+      PyObject_AsReadBuffer(data, &buffer, &buffer_len) == 0) {
+    if (buffer_len % sizeof(Element) != 0) {
       PyErr_Format(PyExc_ValueError,
-                   "str object is not a multiple of %zu bytes",
+                   "byte buffer is not a multiple of %zu bytes",
                    sizeof(Element));
       return;
     }
 
-    int num_elements = size / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
-
-    // Hope there aren't any constructors or destructors involved here.
-    if (size != 0) {
-      const char *ptr = PyString_AsString(data);
-      memcpy(this->_this->p(), ptr, size);
+    if (buffer_len > 0) {
+      this->_this->resize(buffer_len / sizeof(Element));
+      memcpy(this->_this->p(), buffer, buffer_len);
     } else {
       this->_this->clear();
     }
-  } else {
-    Dtool_Raise_TypeError("PointerToArray.set_data() requires a str");
+
+    return;
   }
 #endif
+
+  Dtool_Raise_TypeError("PointerToArray.set_data() requires a buffer object");
 }
 
 /**

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

@@ -23,13 +23,8 @@ ALLOC_DELETED_CHAIN_DEF(CLP(GeomMunger));
 CLP(GeomMunger)::
 CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state) :
   StandardMunger(gsg, state, 4, NT_uint8, C_color),
-  _texture((const TextureAttrib *)state->get_attrib(TextureAttrib::get_class_slot())),
-  _tex_gen((const TexGenAttrib *)state->get_attrib(TexGenAttrib::get_class_slot()))
-{
-  // Set a callback to unregister ourselves when either the Texture or the
-  // TexGen object gets deleted.
-  _texture.set_callback(this);
-  _tex_gen.set_callback(this);
+  _texture(nullptr),
+  _tex_gen(nullptr) {
 
   _flags = 0;
 
@@ -38,6 +33,15 @@ CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state) :
   } else if (gl_parallel_arrays) {
     _flags |= F_parallel_arrays;
   }
+
+  if ((_flags & F_parallel_arrays) == 0) {
+    // Set a callback to unregister ourselves when either the Texture or the
+    // TexGen object gets deleted.
+    _texture = (const TextureAttrib *)state->get_attrib(TextureAttrib::get_class_slot());
+    _tex_gen = (const TexGenAttrib *)state->get_attrib(TexGenAttrib::get_class_slot());
+    _texture.set_callback(this);
+    _tex_gen.set_callback(this);
+  }
 }
 
 /**

+ 31 - 14
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -213,12 +213,20 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     return false;
   }
 
-  if (!_host->begin_frame(FM_parasite, current_thread)) {
-    if (GLCAT.is_debug()) {
-      GLCAT.debug()
-        << get_name() << "'s host is not ready\n";
+  if (_host != nullptr) {
+    if (!_host->begin_frame(FM_parasite, current_thread)) {
+      if (GLCAT.is_debug()) {
+        GLCAT.debug()
+          << get_name() << "'s host is not ready\n";
+      }
+      return false;
+    }
+  } else {
+    // We don't have a host window, which is possible for CocoaGraphicsBuffer.
+    _gsg->set_current_properties(&get_fb_properties());
+    if (!_gsg->begin_frame(current_thread)) {
+      return false;
     }
-    return false;
   }
 
   // Figure out the desired size of the  buffer.
@@ -235,7 +243,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       }
     }
     if (_creation_flags & GraphicsPipe::BF_size_track_host) {
-      if (_host->get_size() != _size) {
+      if (_host != nullptr && _host->get_size() != _size) {
         // We also need to rebuild if we need to change size.
         _needs_rebuild = true;
       }
@@ -356,7 +364,7 @@ rebuild_bitplanes() {
 
   // Calculate bitplane size.  This can be larger than the buffer.
   if (_creation_flags & GraphicsPipe::BF_size_track_host) {
-    if (_host->get_size() != _size) {
+    if (_host != nullptr && _host->get_size() != _size) {
       set_size_and_recalc(_host->get_x_size(),
                           _host->get_y_size());
     }
@@ -1253,7 +1261,11 @@ end_frame(FrameMode mode, Thread *current_thread) {
     generate_mipmaps();
   }
 
-  _host->end_frame(FM_parasite, current_thread);
+  if (_host != nullptr) {
+    _host->end_frame(FM_parasite, current_thread);
+  } else {
+    glgsg->end_frame(current_thread);
+  }
 
   if (mode == FM_render) {
     trigger_flip();
@@ -1315,8 +1327,11 @@ bool CLP(GraphicsBuffer)::
 open_buffer() {
   report_my_gl_errors();
 
-  // Double check that we have a host
-  nassertr(_host != 0, false);
+  // Double check that we have a valid gsg
+  nassertr(_gsg != nullptr, false);
+  if (!_gsg->is_valid()) {
+    return false;
+  }
 
   // Count total color buffers.
   int totalcolor =
@@ -1491,8 +1506,10 @@ open_buffer() {
   _fb_properties.set_back_buffers(0);
   _fb_properties.set_indexed_color(0);
   _fb_properties.set_rgb_color(1);
-  _fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
-  _fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
+  if (_host != nullptr) {
+    _fb_properties.set_force_hardware(_host->get_fb_properties().get_force_hardware());
+    _fb_properties.set_force_software(_host->get_fb_properties().get_force_software());
+  }
 
   _is_valid = true;
   _needs_rebuild = true;
@@ -1508,7 +1525,7 @@ open_buffer() {
  */
 GraphicsOutput *CLP(GraphicsBuffer)::
 get_host() {
-  return _host;
+  return (_host != nullptr) ? _host : this;
 }
 
 /**
@@ -1699,7 +1716,7 @@ report_my_errors(int line, const char *file) {
  */
 void CLP(GraphicsBuffer)::
 check_host_valid() {
-  if ((_host == 0)||(!_host->is_valid())) {
+  if (_host != nullptr && !_host->is_valid()) {
     _rb_data_size_bytes = 0;
     if (_rb_context != NULL) {
       // We must delete this object first, because when the GSG destructs, so

+ 0 - 2
panda/src/glstuff/glGraphicsBuffer_src.h

@@ -86,8 +86,6 @@ protected:
 
   void report_my_errors(int line, const char *file);
 
-private:
-
   void bind_slot(int layer, bool rb_resize, Texture **attach,
                  RenderTexturePlane plane, GLenum attachpoint);
   void bind_slot_multisample(bool rb_resize, Texture **attach,

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

@@ -155,7 +155,11 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 // drawing GUIs and such.
 static const string default_vshader =
 #ifndef OPENGLES
+#ifdef __APPLE__ // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
+#endif
   "in vec4 p3d_Vertex;\n"
   "in vec4 p3d_Color;\n"
   "in vec2 p3d_MultiTexCoord0;\n"
@@ -179,7 +183,11 @@ static const string default_vshader =
 
 static const string default_fshader =
 #ifndef OPENGLES
+#ifdef __APPLE__  // Apple's GL 3.2 contexts require at least GLSL 1.50.
+  "#version 150\n"
+#else
   "#version 130\n"
+#endif
   "in vec2 texcoord;\n"
   "in vec4 color;\n"
   "out vec4 p3d_FragColor;\n"
@@ -6460,9 +6468,6 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     break;
 
   case Texture::F_depth_component24:
-    component_type = Texture::T_unsigned_int;
-    break;
-
   case Texture::F_depth_component32:
     component_type = Texture::T_float;
     break;
@@ -7556,6 +7561,36 @@ bind_light(Spotlight *light_obj, const NodePath &light, int light_id) {
 }
 #endif  // SUPPORT_FIXED_FUNCTION
 
+/**
+ * Creates a depth buffer for shadow mapping.  A derived GSG can override this
+ * if it knows that a particular buffer type works best for shadow rendering.
+ */
+GraphicsOutput *CLP(GraphicsStateGuardian)::
+make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host) {
+  // We override this to circumvent the fact that GraphicsEngine::make_output
+  // can only be called from the app thread.
+  if (!_supports_framebuffer_object) {
+    return GraphicsStateGuardian::make_shadow_buffer(light, tex, host);
+  }
+
+  bool is_point = light->is_of_type(PointLight::get_class_type());
+
+  // Determine the properties for creating the depth buffer.
+  FrameBufferProperties fbp;
+  fbp.set_depth_bits(shadow_depth_bits);
+
+  WindowProperties props = WindowProperties::size(light->get_shadow_buffer_size());
+  int flags = GraphicsPipe::BF_refuse_window;
+  if (is_point) {
+    flags |= GraphicsPipe::BF_size_square;
+  }
+
+  CLP(GraphicsBuffer) *sbuffer = new GLGraphicsBuffer(get_engine(), get_pipe(), light->get_name(), fbp, props, flags, this, host);
+  sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
+  get_engine()->add_window(sbuffer, light->get_shadow_buffer_sort());
+  return sbuffer;
+}
+
 #ifdef SUPPORT_IMMEDIATE_MODE
 /**
  * Uses the ImmediateModeSender to draw a series of primitives of the
@@ -12326,20 +12361,20 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
               if (_supports_clear_texture) {
                 // We can do that with the convenient glClearTexImage
                 // function.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
                 _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
-                                 component_type, (void *)clear_data.data());
+                                 component_type, (void *)&clear_data[0]);
                 continue;
               }
             } else {
               if (_supports_clear_buffer) {
                 // For buffer textures we need to clear the underlying
                 // storage.
-                string clear_data = tex->get_clear_data();
+                vector_uchar clear_data = tex->get_clear_data();
 
                 _glClearBufferData(GL_TEXTURE_BUFFER, internal_format, external_format,
-                                   component_type, (const void *)clear_data.data());
+                                   component_type, (const void *)&clear_data[0]);
                 continue;
               }
             }

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

@@ -381,6 +381,8 @@ public:
                           int light_id);
 #endif
 
+  virtual GraphicsOutput *make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host);
+
   LVecBase4 get_light_color(Light *light) const;
 
 #ifdef SUPPORT_IMMEDIATE_MODE

+ 8 - 13
panda/src/gobj/geom.I

@@ -512,22 +512,17 @@ CData() :
  *
  */
 INLINE Geom::CData::
-CData(const Geom::CData &copy) :
-  _data(copy._data),
-  _primitives(copy._primitives),
-  _primitive_type(copy._primitive_type),
-  _shade_model(copy._shade_model),
-  _geom_rendering(copy._geom_rendering),
-  _modified(copy._modified),
-  _internal_bounds(copy._internal_bounds),
-  _nested_vertices(copy._nested_vertices),
-  _internal_bounds_stale(copy._internal_bounds_stale),
-  _bounds_type(copy._bounds_type),
-  _user_bounds(copy._user_bounds)
+CData(GeomVertexData *data) :
+  _data(data),
+  _primitive_type(PT_none),
+  _shade_model(SM_uniform),
+  _geom_rendering(0),
+  _nested_vertices(0),
+  _internal_bounds_stale(true),
+  _bounds_type(BoundingVolume::BT_default)
 {
 }
 
-
 /**
  *
  */

+ 13 - 21
panda/src/gobj/geom.cxx

@@ -46,13 +46,7 @@ make_cow_copy() {
  *
  */
 Geom::
-Geom(const GeomVertexData *data) {
-  // Let's ensure the vertex data gets set on all stages at once.
-  OPEN_ITERATE_ALL_STAGES(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
-    cdata->_data = (GeomVertexData *)data;
-  }
-  CLOSE_ITERATE_ALL_STAGES(_cycler);
+Geom(const GeomVertexData *data) : _cycler(CData((GeomVertexData *)data)) {
 }
 
 /**
@@ -631,7 +625,7 @@ unify_in_place(int max_indices, bool preserve_order) {
     } else {
       // We have already encountered another primitive of this type.  Combine
       // them.
-      combine_primitives((*npi).second, primitive, current_thread);
+      combine_primitives((*npi).second, move(primitive), current_thread);
     }
   }
 
@@ -1491,31 +1485,29 @@ reset_geom_rendering(Geom::CData *cdata) {
  * is modified to append the vertices from b_prim, which is unmodified.
  */
 void Geom::
-combine_primitives(GeomPrimitive *a_prim, const GeomPrimitive *b_prim,
+combine_primitives(GeomPrimitive *a_prim, CPT(GeomPrimitive) b_prim,
                    Thread *current_thread) {
   nassertv(a_prim != b_prim);
   nassertv(a_prim->get_type() == b_prim->get_type());
 
-  CPT(GeomPrimitive) b_prim2 = b_prim;
-
-  if (a_prim->get_index_type() != b_prim2->get_index_type()) {
-    GeomPrimitive::NumericType index_type = max(a_prim->get_index_type(), b_prim2->get_index_type());
+  if (a_prim->get_index_type() != b_prim->get_index_type()) {
+    GeomPrimitive::NumericType index_type = max(a_prim->get_index_type(), b_prim->get_index_type());
     a_prim->set_index_type(index_type);
-    if (b_prim2->get_index_type() != index_type) {
-      PT(GeomPrimitive) b_prim_copy = b_prim2->make_copy();
+    if (b_prim->get_index_type() != index_type) {
+      PT(GeomPrimitive) b_prim_copy = b_prim->make_copy();
       b_prim_copy->set_index_type(index_type);
-      b_prim2 = b_prim_copy;
+      b_prim = b_prim_copy;
     }
   }
 
-  if (!b_prim2->is_indexed()) {
-    PT(GeomPrimitive) b_prim_copy = b_prim2->make_copy();
+  if (!b_prim->is_indexed()) {
+    PT(GeomPrimitive) b_prim_copy = b_prim->make_copy();
     b_prim_copy->make_indexed();
-    b_prim2 = b_prim_copy;
+    b_prim = b_prim_copy;
   }
 
   PT(GeomVertexArrayData) a_vertices = a_prim->modify_vertices();
-  CPT(GeomVertexArrayData) b_vertices = b_prim2->get_vertices();
+  CPT(GeomVertexArrayData) b_vertices = b_prim->get_vertices();
 
   if (a_prim->requires_unused_vertices()) {
     GeomVertexReader index(b_vertices, 0);
@@ -1536,7 +1528,7 @@ combine_primitives(GeomPrimitive *a_prim, const GeomPrimitive *b_prim,
   if (a_prim->is_composite()) {
     // Also copy the ends array.
     PTA_int a_ends = a_prim->modify_ends();
-    CPTA_int b_ends = b_prim2->get_ends();
+    CPTA_int b_ends = b_prim->get_ends();
     for (size_t i = 0; i < b_ends.size(); ++i) {
       a_ends.push_back(b_ends[i] + orig_a_vertices);
     }

+ 3 - 2
panda/src/gobj/geom.h

@@ -196,7 +196,7 @@ private:
 
   void reset_geom_rendering(CData *cdata);
 
-  void combine_primitives(GeomPrimitive *a_prim, const GeomPrimitive *b_prim,
+  void combine_primitives(GeomPrimitive *a_prim, CPT(GeomPrimitive) b_prim,
                           Thread *current_thread);
 
 private:
@@ -302,7 +302,8 @@ private:
   class EXPCL_PANDA_GOBJ CData : public CycleData {
   public:
     INLINE CData();
-    INLINE CData(const CData &copy);
+    INLINE CData(GeomVertexData *data);
+
     ALLOC_DELETED_CHAIN(CData);
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;

+ 14 - 2
panda/src/gobj/geomVertexArrayData.I

@@ -229,8 +229,20 @@ mark_used() {
  *
  */
 INLINE GeomVertexArrayData::CData::
-CData() :
-  _usage_hint(UH_unspecified),
+CData(UsageHint usage_hint) :
+  _usage_hint(usage_hint),
+  _rw_lock("GeomVertexArrayData::CData::_rw_lock")
+{
+}
+
+/**
+ *
+ */
+INLINE GeomVertexArrayData::CData::
+CData(GeomVertexArrayData::CData &&from) NOEXCEPT :
+  _usage_hint(move(from._usage_hint)),
+  _buffer(move(from._buffer)),
+  _modified(move(from._modified)),
   _rw_lock("GeomVertexArrayData::CData::_rw_lock")
 {
 }

+ 5 - 12
panda/src/gobj/geomVertexArrayData.cxx

@@ -77,16 +77,10 @@ GeomVertexArrayData::
 GeomVertexArrayData(const GeomVertexArrayFormat *array_format,
                     GeomVertexArrayData::UsageHint usage_hint) :
   SimpleLruPage(0),
-  _array_format(array_format)
+  _array_format(array_format),
+  _cycler(CData(usage_hint)),
+  _contexts(nullptr)
 {
-  OPEN_ITERATE_ALL_STAGES(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
-    cdata->_usage_hint = usage_hint;
-  }
-  CLOSE_ITERATE_ALL_STAGES(_cycler);
-
-  _contexts = NULL;
-
   set_lru_size(0);
   nassertv(_array_format->is_registered());
 }
@@ -99,10 +93,9 @@ GeomVertexArrayData(const GeomVertexArrayData &copy) :
   CopyOnWriteObject(copy),
   SimpleLruPage(copy),
   _array_format(copy._array_format),
-  _cycler(copy._cycler)
+  _cycler(copy._cycler),
+  _contexts(nullptr)
 {
-  _contexts = NULL;
-
   copy.mark_used_lru();
 
   set_lru_size(get_data_size_bytes());

+ 2 - 1
panda/src/gobj/geomVertexArrayData.h

@@ -150,7 +150,8 @@ private:
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_GOBJ CData : public CycleData {
   public:
-    INLINE CData();
+    INLINE CData(UsageHint usage_hint = UH_unspecified);
+    INLINE CData(CData &&from) NOEXCEPT;
     INLINE CData(const CData &copy);
     INLINE void operator = (const CData &copy);
 

+ 7 - 10
panda/src/gobj/geomVertexData.I

@@ -583,17 +583,14 @@ CData() :
  *
  */
 INLINE GeomVertexData::CData::
-CData(const GeomVertexData::CData &copy) :
-  _usage_hint(copy._usage_hint),
-  _format(copy._format),
-  _arrays(copy._arrays),
-  _transform_table(copy._transform_table),
-  _transform_blend_table(copy._transform_blend_table),
-  _slider_table(copy._slider_table),
-  _animated_vertices(copy._animated_vertices),
-  _animated_vertices_modified(copy._animated_vertices_modified),
-  _modified(copy._modified)
+CData(const GeomVertexFormat *format, GeomVertexData::UsageHint usage_hint) :
+  _format(format),
+  _usage_hint(usage_hint)
 {
+  size_t num_arrays = format->get_num_arrays();
+  for (size_t i = 0; i < num_arrays; ++i) {
+    _arrays.push_back(new GeomVertexArrayData(format->get_array(i), usage_hint));
+  }
 }
 
 /**

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

@@ -67,24 +67,10 @@ GeomVertexData(const string &name,
   _char_pcollector(PStatCollector(_animation_pcollector, name)),
   _skinning_pcollector(_char_pcollector, "Skinning"),
   _morphs_pcollector(_char_pcollector, "Morphs"),
-  _blends_pcollector(_char_pcollector, "Calc blends")
+  _blends_pcollector(_char_pcollector, "Calc blends"),
+  _cycler(GeomVertexData::CData(format, usage_hint))
 {
   nassertv(format->is_registered());
-
-  // Create some empty arrays as required by the format.  Let's ensure the
-  // vertex data gets set on all stages at once.
-  OPEN_ITERATE_ALL_STAGES(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
-    cdata->_format = format;
-    cdata->_usage_hint = usage_hint;
-    int num_arrays = format->get_num_arrays();
-    for (int i = 0; i < num_arrays; i++) {
-      PT(GeomVertexArrayData) array = new GeomVertexArrayData
-        (format->get_array(i), usage_hint);
-      cdata->_arrays.push_back(array.p());
-    }
-  }
-  CLOSE_ITERATE_ALL_STAGES(_cycler);
 }
 
 /**

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

@@ -293,7 +293,8 @@ private:
   class EXPCL_PANDA_GOBJ CData : public CycleData {
   public:
     INLINE CData();
-    INLINE CData(const CData &copy);
+    INLINE CData(const GeomVertexFormat *format, UsageHint usage_hint);
+
     ALLOC_DELETED_CHAIN(CData);
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;

+ 4 - 2
panda/src/gobj/shaderBuffer.I

@@ -19,7 +19,8 @@ INLINE ShaderBuffer::
 ShaderBuffer(const string &name, uint64_t size, UsageHint usage_hint) :
   Namable(name),
   _data_size_bytes(size),
-  _usage_hint(usage_hint) {
+  _usage_hint(usage_hint),
+  _contexts(nullptr) {
 }
 
 /**
@@ -31,7 +32,8 @@ ShaderBuffer(const string &name, pvector<unsigned char> initial_data, UsageHint
   Namable(name),
   _data_size_bytes(initial_data.size()),
   _usage_hint(usage_hint),
-  _initial_data(initial_data) {
+  _initial_data(initial_data),
+  _contexts(nullptr) {
 }
 
 /**

+ 8 - 0
panda/src/gobj/shaderBuffer.cxx

@@ -16,6 +16,14 @@
 
 TypeHandle ShaderBuffer::_type_handle;
 
+/**
+ * Destructor.
+ */
+ShaderBuffer::
+~ShaderBuffer() {
+  release_all();
+}
+
 /**
  *
  */

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

@@ -32,6 +32,8 @@ private:
   INLINE ShaderBuffer() DEFAULT_CTOR;
 
 PUBLISHED:
+  ~ShaderBuffer();
+
   INLINE explicit ShaderBuffer(const string &name, uint64_t size, UsageHint usage_hint);
   INLINE explicit ShaderBuffer(const string &name, pvector<unsigned char> initial_data, UsageHint usage_hint);
 

+ 59 - 4
panda/src/gobj/texture.I

@@ -279,12 +279,12 @@ clear_clear_color() {
  * Returns the raw image data for a single pixel if it were set to the clear
  * color.
  */
-INLINE string Texture::
+INLINE vector_uchar Texture::
 get_clear_data() const {
   CDReader cdata(_cycler);
-  unsigned char data[16];
-  size_t size = do_get_clear_data(cdata, data);
-  return string((char *)data, size);
+  vector_uchar data(16);
+  data.resize(do_get_clear_data(cdata, &data[0]));
+  return data;
 }
 
 /**
@@ -2324,6 +2324,61 @@ get_unsigned_short(const unsigned char *&p) {
   return (double)v.us / 65535.0;
 }
 
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * unsigned ints.
+ */
+INLINE double Texture::
+get_unsigned_int(const unsigned char *&p) {
+  union {
+    unsigned int ui;
+    uchar uc[4];
+  } v;
+  v.uc[0] = (*p++);
+  v.uc[1] = (*p++);
+  v.uc[2] = (*p++);
+  v.uc[3] = (*p++);
+  return (double)v.ui / 4294967295.0;
+}
+
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * floats.
+ */
+INLINE double Texture::
+get_float(const unsigned char *&p) {
+  double v = *((float *)p);
+  p += 4;
+  return v;
+}
+
+/**
+ * This is used by store() to retrieve the next consecutive component value
+ * from the indicated element of the array, which is taken to be an array of
+ * half-floats.
+ */
+INLINE double Texture::
+get_half_float(const unsigned char *&p) {
+  union {
+    uint32_t ui;
+    float uf;
+  } v;
+  uint16_t in = *(uint16_t *)p;
+  p += 2;
+  uint32_t t1 = in & 0x7fff; // Non-sign bits
+  uint32_t t2 = in & 0x8000; // Sign bit
+  uint32_t t3 = in & 0x7c00; // Exponent
+  t1 <<= 13; // Align mantissa on MSB
+  t2 <<= 16; // Shift sign bit into position
+  t1 += 0x38000000; // Adjust bias
+  t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero
+  t1 |= t2; // Re-insert sign bit
+  v.ui = t1;
+  return v.uf;
+}
+
 /**
  * Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz,
  * false otherwise.

+ 105 - 31
panda/src/gobj/texture.cxx

@@ -5088,7 +5088,8 @@ do_store_one(CData *cdata, PNMImage &pnmimage, int z, int n) {
   return convert_to_pnmimage(pnmimage,
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_y_size(cdata, n),
-                             cdata->_num_components, cdata->_component_width,
+                             cdata->_num_components, cdata->_component_type,
+                             is_srgb(cdata->_format),
                              cdata->_ram_images[n]._image,
                              do_get_ram_mipmap_page_size(cdata, n), z);
 }
@@ -5111,12 +5112,14 @@ do_store_one(CData *cdata, PfmFile &pfm, int z, int n) {
   if (cdata->_component_type != T_float) {
     // PfmFile by way of PNMImage.
     PNMImage pnmimage;
-    bool success = convert_to_pnmimage(pnmimage,
-                                       do_get_expected_mipmap_x_size(cdata, n),
-                                       do_get_expected_mipmap_y_size(cdata, n),
-                                       cdata->_num_components, cdata->_component_width,
-                                       cdata->_ram_images[n]._image,
-                                       do_get_ram_mipmap_page_size(cdata, n), z);
+    bool success =
+      convert_to_pnmimage(pnmimage,
+                          do_get_expected_mipmap_x_size(cdata, n),
+                          do_get_expected_mipmap_y_size(cdata, n),
+                          cdata->_num_components, cdata->_component_type,
+                          is_srgb(cdata->_format),
+                          cdata->_ram_images[n]._image,
+                          do_get_ram_mipmap_page_size(cdata, n), z);
     if (!success) {
       return false;
     }
@@ -5584,10 +5587,28 @@ do_get_clear_data(const CData *cdata, unsigned char *into) const {
   nassertr(cdata->_has_clear_color, 0);
   nassertr(cdata->_num_components <= 4, 0);
 
-  // TODO: encode the color into the sRGB color space if used
   switch (cdata->_component_type) {
   case T_unsigned_byte:
-    {
+    if (is_srgb(cdata->_format)) {
+      xel color;
+      xelval alpha;
+      encode_sRGB_uchar(cdata->_clear_color, color, alpha);
+      switch (cdata->_num_components) {
+      case 2:
+        into[1] = (unsigned char)color.g;
+      case 1:
+        into[0] = (unsigned char)color.r;
+        break;
+      case 4:
+        into[3] = (unsigned char)alpha;
+      case 3: // BGR <-> RGB
+        into[0] = (unsigned char)color.b;
+        into[1] = (unsigned char)color.g;
+        into[2] = (unsigned char)color.r;
+        break;
+      }
+      break;
+    } else {
       LColor scaled = cdata->_clear_color.fmin(LColor(1)).fmax(LColor::zero());
       scaled *= 255;
       switch (cdata->_num_components) {
@@ -8037,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
  */
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                    int num_components, int component_width,
-                    CPTA_uchar image, size_t page_size, int z) {
+                    int num_components, ComponentType component_type,
+                    bool is_srgb, CPTA_uchar image, size_t page_size, int z) {
   xelval maxval = 0xff;
-  if (component_width > 1) {
+  if (component_type != T_unsigned_byte && component_type != T_byte) {
     maxval = 0xffff;
   }
-  pnmimage.clear(x_size, y_size, num_components, maxval);
+  ColorSpace color_space = is_srgb ? CS_sRGB : CS_linear;
+  pnmimage.clear(x_size, y_size, num_components, maxval, nullptr, color_space);
   bool has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
 
   int idx = page_size * z;
   nassertr(idx + page_size <= image.size(), false);
-  const unsigned char *p = &image[idx];
 
-  if (component_width == 1) {
-    xel *array = pnmimage.get_array();
+  xel *array = pnmimage.get_array();
+  xelval *alpha = pnmimage.get_alpha_array();
+
+  switch (component_type) {
+  case T_unsigned_byte:
     if (is_grayscale) {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8072,9 +8096,10 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     } else {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8095,29 +8120,78 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     }
+    break;
 
-  } else if (component_width == 2) {
-    for (int j = y_size-1; j >= 0; j--) {
-      for (int i = 0; i < x_size; i++) {
-        if (is_grayscale) {
-          pnmimage.set_gray(i, j, get_unsigned_short(p));
-        } else {
-          pnmimage.set_blue(i, j, get_unsigned_short(p));
-          pnmimage.set_green(i, j, get_unsigned_short(p));
-          pnmimage.set_red(i, j, get_unsigned_short(p));
+  case T_unsigned_short:
+    {
+      const uint16_t *p = (const uint16_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], *p++);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], *p++);
+            PPM_PUTR(row[i], *p++);
+          }
+          if (has_alpha) {
+            alpha_row[i] = *p++;
+          }
         }
-        if (has_alpha) {
-          pnmimage.set_alpha(i, j, get_unsigned_short(p));
+      }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
+    }
+    break;
+
+  case T_unsigned_int:
+    {
+      const uint32_t *p = (const uint32_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], (*p++) >> 16u);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], (*p++) >> 16u);
+            PPM_PUTR(row[i], (*p++) >> 16u);
+          }
+          if (has_alpha) {
+            alpha_row[i] = (*p++) >> 16u;
+          }
+        }
+      }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
+    }
+    break;
+
+  case T_half_float:
+    {
+      const unsigned char *p = &image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        for (int i = 0; i < x_size; i++) {
+          pnmimage.set_blue(i, j, get_half_float(p));
+          if (!is_grayscale) {
+            pnmimage.set_green(i, j, get_half_float(p));
+            pnmimage.set_red(i, j, get_half_float(p));
+          }
+          if (has_alpha) {
+            pnmimage.set_alpha(i, j, get_half_float(p));
+          }
         }
       }
+      nassertr(p == &image[idx] + page_size, false);
     }
+    break;
 
-  } else {
+  default:
     return false;
   }
 
-  nassertr(p == &image[idx] + page_size, false);
   return true;
 }
 

+ 6 - 2
panda/src/gobj/texture.h

@@ -265,7 +265,7 @@ PUBLISHED:
   INLINE LColor get_clear_color() const;
   INLINE void set_clear_color(const LColor &color);
   INLINE void clear_clear_color();
-  INLINE string get_clear_data() const;
+  INLINE vector_uchar get_clear_data() const;
   MAKE_PROPERTY2(clear_color, has_clear_color, get_clear_color,
                               set_clear_color, clear_clear_color);
 
@@ -801,7 +801,8 @@ private:
                                int z, const PfmFile &pfm,
                                int num_components, int component_width);
   static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                                  int num_components, int component_width,
+                                  int num_components,
+                                  ComponentType component_type, bool is_srgb,
                                   CPTA_uchar image, size_t page_size,
                                   int z);
   static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
@@ -856,6 +857,9 @@ private:
   INLINE static void store_scaled_short(unsigned char *&p, int value, double scale);
   INLINE static double get_unsigned_byte(const unsigned char *&p);
   INLINE static double get_unsigned_short(const unsigned char *&p);
+  INLINE static double get_unsigned_int(const unsigned char *&p);
+  INLINE static double get_float(const unsigned char *&p);
+  INLINE static double get_half_float(const unsigned char *&p);
 
   INLINE static bool is_txo_filename(const Filename &fullpath);
   INLINE static bool is_dds_filename(const Filename &fullpath);

+ 55 - 2
panda/src/gobj/texturePeeker.cxx

@@ -82,6 +82,18 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     _get_component = Texture::get_unsigned_short;
     break;
 
+  case Texture::T_unsigned_int:
+    _get_component = Texture::get_unsigned_int;
+    break;
+
+  case Texture::T_float:
+    _get_component = Texture::get_float;
+    break;
+
+  case Texture::T_half_float:
+    _get_component = Texture::get_half_float;
+    break;
+
   default:
     // Not supported.
     _image.clear();
@@ -123,7 +135,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
 
   case Texture::F_rgb:
-  case Texture::F_srgb:
   case Texture::F_rgb5:
   case Texture::F_rgb8:
   case Texture::F_rgb12:
@@ -135,7 +146,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     break;
 
   case Texture::F_rgba:
-  case Texture::F_srgb_alpha:
   case Texture::F_rgbm:
   case Texture::F_rgba4:
   case Texture::F_rgba5:
@@ -146,6 +156,25 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   case Texture::F_rgb10_a2:
     _get_texel = get_texel_rgba;
     break;
+
+  case Texture::F_srgb:
+    if (_component_type == Texture::T_unsigned_byte) {
+      _get_texel = get_texel_srgb;
+    } else {
+      gobj_cat.error()
+        << "sRGB texture should have component type T_unsigned_byte\n";
+    }
+    break;
+
+  case Texture::F_srgb_alpha:
+    if (_component_type == Texture::T_unsigned_byte) {
+      _get_texel = get_texel_srgba;
+    } else {
+      gobj_cat.error()
+        << "sRGB texture should have component type T_unsigned_byte\n";
+    }
+    break;
+
   default:
     // Not supported.
     gobj_cat.error() << "Unsupported texture peeker format: "
@@ -570,3 +599,27 @@ get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_com
   color[0] = (*get_component)(p);
   color[3] = (*get_component)(p);
 }
+
+/**
+ * Gets the color of the texel at byte p, given that the texture is in format
+ * F_srgb or similar.
+ */
+void TexturePeeker::
+get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
+  color[2] = decode_sRGB_float(*p++);
+  color[1] = decode_sRGB_float(*p++);
+  color[0] = decode_sRGB_float(*p++);
+  color[3] = 1.0f;
+}
+
+/**
+ * Gets the color of the texel at byte p, given that the texture is in format
+ * F_srgb_alpha or similar.
+ */
+void TexturePeeker::
+get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
+  color[2] = decode_sRGB_float(*p++);
+  color[1] = decode_sRGB_float(*p++);
+  color[0] = decode_sRGB_float(*p++);
+  color[3] = (*get_component)(p);
+}

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

@@ -79,6 +79,8 @@ private:
   static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
+  static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
+  static void get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
 
   int _x_size;
   int _y_size;

+ 23 - 0
panda/src/gobj/texture_ext.cxx

@@ -84,6 +84,29 @@ set_ram_image(PyObject *image, Texture::CompressionMode compression,
   }
 #endif
 
+#if PY_MAJOR_VERSION < 3
+  // The old, deprecated buffer interface, as used by eg. the array module.
+  const void *buffer;
+  Py_ssize_t buffer_len;
+  if (!PyUnicode_CheckExact(image) &&
+      PyObject_AsReadBuffer(image, &buffer, &buffer_len) == 0) {
+    if (compression == Texture::CM_off) {
+      int component_width = _this->get_component_width();
+      if (buffer_len % component_width != 0) {
+        PyErr_Format(PyExc_ValueError,
+                    "byte buffer is not a multiple of %d bytes",
+                    component_width);
+        return;
+      }
+    }
+
+    PTA_uchar data = PTA_uchar::empty_array(buffer_len, Texture::get_class_type());
+    memcpy(data.p(), buffer, buffer_len);
+    _this->set_ram_image(MOVE(data), compression, page_size);
+    return;
+  }
+#endif
+
   Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image", "CPTA_uchar or buffer");
 }
 

+ 16 - 0
panda/src/linmath/lmatrix4_src.I

@@ -1706,3 +1706,19 @@ INLINE_LINMATH int FLOATNAME(UnalignedLMatrix4)::
 get_num_components() const {
   return 16;
 }
+
+/**
+ *
+ */
+INLINE_LINMATH bool FLOATNAME(UnalignedLMatrix4)::
+operator == (const FLOATNAME(UnalignedLMatrix4) &other) const {
+  return memcmp(get_data(), other.get_data(), sizeof(FLOATTYPE) * 16) == 0;
+}
+
+/**
+ *
+ */
+INLINE_LINMATH bool FLOATNAME(UnalignedLMatrix4)::
+operator != (const FLOATNAME(UnalignedLMatrix4) &other) const {
+  return !operator == (other);
+}

+ 3 - 0
panda/src/linmath/lmatrix4_src.h

@@ -345,6 +345,9 @@ PUBLISHED:
   INLINE_LINMATH const FLOATTYPE *get_data() const;
   INLINE_LINMATH int get_num_components() const;
 
+  INLINE_LINMATH bool operator == (const FLOATNAME(UnalignedLMatrix4) &other) const;
+  INLINE_LINMATH bool operator != (const FLOATNAME(UnalignedLMatrix4) &other) const;
+
 public:
   typedef UNALIGNED_LINMATH_MATRIX(FLOATTYPE, 4, 4) UMatrix4;
   UMatrix4 _m;

+ 23 - 0
panda/src/linmath/lquaternion_src.cxx

@@ -26,6 +26,29 @@ pure_imaginary(const FLOATNAME(LVector3) &v) {
   return FLOATNAME(LQuaternion)(0, v[0], v[1], v[2]);
 }
 
+/**
+ * Returns a new quaternion that represents this quaternion raised to the
+ * given power.
+ */
+FLOATNAME(LQuaternion) FLOATNAME(LQuaternion)::
+__pow__(FLOATTYPE power) const {
+  if (IS_NEARLY_ZERO(power)) {
+    return FLOATNAME(LQuaternion)(1, 0, 0, 0);
+  }
+
+  FLOATTYPE l = length();
+  FLOATTYPE norm = _v(0) / l;
+  if (IS_NEARLY_EQUAL(cabs(norm), (FLOATTYPE)1)) {
+    return FLOATNAME(LQuaternion)(cpow(_v(0), power), 0, 0, 0);
+  }
+
+  FLOATTYPE angle = acos(norm);
+  FLOATTYPE angle2 = angle * power;
+  FLOATTYPE mag = cpow(l, power - 1);
+  FLOATTYPE mult = mag * (sin(angle2) / sin(angle));
+  return FLOATNAME(LQuaternion)(cos(angle2) * mag * l, _v(1) * mult, _v(2) * mult, _v(3) * mult);
+}
+
 /**
  * Based on the quat lib from VRPN.
  */

+ 2 - 0
panda/src/linmath/lquaternion_src.h

@@ -57,6 +57,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LMatrix3) operator *(const FLOATNAME(LMatrix3) &);
   INLINE_LINMATH FLOATNAME(LMatrix4) operator *(const FLOATNAME(LMatrix4) &);
 
+  FLOATNAME(LQuaternion) __pow__(FLOATTYPE) const;
+
   INLINE_LINMATH bool almost_equal(
       const FLOATNAME(LQuaternion) &other) const;
   INLINE_LINMATH bool almost_equal(

+ 19 - 0
panda/src/linmath/lvecBase4_src.I

@@ -971,3 +971,22 @@ INLINE_LINMATH const FLOATTYPE *FLOATNAME(UnalignedLVecBase4)::
 get_data() const {
   return &_v(0);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH bool FLOATNAME(UnalignedLVecBase4)::
+operator == (const FLOATNAME(UnalignedLVecBase4) &other) const {
+  return (_v(0) == other._v(0) &&
+          _v(1) == other._v(1) &&
+          _v(2) == other._v(2) &&
+          _v(3) == other._v(3));
+}
+
+/**
+ *
+ */
+INLINE_LINMATH bool FLOATNAME(UnalignedLVecBase4)::
+operator != (const FLOATNAME(UnalignedLVecBase4) &other) const {
+  return !operator == (other);
+}

+ 3 - 0
panda/src/linmath/lvecBase4_src.h

@@ -243,6 +243,9 @@ PUBLISHED:
   INLINE_LINMATH const FLOATTYPE *get_data() const;
   CONSTEXPR static int get_num_components() { return 4; }
 
+  INLINE_LINMATH bool operator == (const FLOATNAME(UnalignedLVecBase4) &other) const;
+  INLINE_LINMATH bool operator != (const FLOATNAME(UnalignedLVecBase4) &other) const;
+
 public:
   typedef FLOATTYPE numeric_type;
   typedef UNALIGNED_LINMATH_MATRIX(FLOATTYPE, 1, 4) UVector4;

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

@@ -17,7 +17,6 @@
 #include "config_movies.h"
 
 #define DR_FLAC_IMPLEMENTATION
-#define DR_FLAC_NO_STDIO
 extern "C" {
   #include "dr_flac.h"
 }

+ 1 - 0
panda/src/pgraph/textureAttrib.cxx

@@ -825,6 +825,7 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
     }
   }
   _on_stages.sort();
+  _off_stages.sort();
   _sort_seq = UpdateSeq::old();
   _filtered_seq = UpdateSeq::old();
 

+ 16 - 1
panda/src/pgraphnodes/lightLensNode.I

@@ -41,6 +41,9 @@ set_shadow_caster(bool caster) {
   }
   _shadow_caster = caster;
   set_active(caster);
+  if (caster) {
+    setup_shadow_map();
+  }
 }
 
 /**
@@ -65,6 +68,17 @@ set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_so
     _sb_sort = buffer_sort;
   }
   set_active(caster);
+  if (caster) {
+    setup_shadow_map();
+  }
+}
+
+/**
+ * Returns the sort of the shadow buffer to be created for this light source.
+ */
+INLINE int LightLensNode::
+get_shadow_buffer_sort() const {
+  return _sb_sort;
 }
 
 /**
@@ -82,8 +96,9 @@ INLINE void LightLensNode::
 set_shadow_buffer_size(const LVecBase2i &size) {
   if (size != _sb_size) {
     clear_shadow_buffers();
+    _sb_size = size;
+    setup_shadow_map();
   }
-  _sb_size = size;
 }
 
 /**

+ 33 - 7
panda/src/pgraphnodes/lightLensNode.cxx

@@ -67,6 +67,9 @@ LightLensNode(const LightLensNode &copy) :
   _has_specular_color(copy._has_specular_color),
   _attrib_count(0)
 {
+  if (_shadow_caster) {
+    setup_shadow_map();
+  }
 }
 
 /**
@@ -75,20 +78,43 @@ LightLensNode(const LightLensNode &copy) :
  */
 void LightLensNode::
 clear_shadow_buffers() {
+  if (_shadow_map) {
+    // Clear it to all ones, so that any shaders that might still be using
+    // it will see the shadows being disabled.
+    _shadow_map->clear_image();
+  }
+
   ShadowBuffers::iterator it;
   for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) {
-    PT(Texture) tex = (*it).second->get_texture();
-    if (tex) {
-      // Clear it to all ones, so that any shaders that might still be using
-      // it will see the shadows being disabled.
-      tex->set_clear_color(LColor(1));
-      tex->clear_image();
-    }
     (*it).first->remove_window((*it).second);
   }
   _sbuffers.clear();
 }
 
+/**
+ * Creates the shadow map texture.  Can be overridden.
+ */
+void LightLensNode::
+setup_shadow_map() {
+  if (_shadow_map != nullptr &&
+      _shadow_map->get_x_size() == _sb_size[0] &&
+      _shadow_map->get_y_size() == _sb_size[1]) {
+    // Nothing to do.
+    return;
+  }
+
+  if (_shadow_map == nullptr) {
+    _shadow_map = new Texture(get_name());
+  }
+
+  _shadow_map->setup_2d_texture(_sb_size[0], _sb_size[1], Texture::T_unsigned_byte, Texture::F_depth_component);
+  _shadow_map->set_clear_color(LColor(1));
+  _shadow_map->set_wrap_u(SamplerState::WM_border_color);
+  _shadow_map->set_wrap_v(SamplerState::WM_border_color);
+  _shadow_map->set_border_color(LColor(1));
+  _shadow_map->set_minfilter(SamplerState::FT_shadow);
+  _shadow_map->set_magfilter(SamplerState::FT_shadow);
+}
 
 /**
  * This is called when the light is added to a LightAttrib.

+ 5 - 1
panda/src/pgraphnodes/lightLensNode.h

@@ -41,6 +41,8 @@ PUBLISHED:
   INLINE void set_shadow_caster(bool caster);
   INLINE void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10);
 
+  INLINE int get_shadow_buffer_sort() const;
+
   INLINE LVecBase2i get_shadow_buffer_size() const;
   INLINE void set_shadow_buffer_size(const LVecBase2i &size);
 
@@ -53,12 +55,15 @@ PUBLISHED:
 protected:
   LightLensNode(const LightLensNode &copy);
   void clear_shadow_buffers();
+  virtual void setup_shadow_map();
 
   LVecBase2i _sb_size;
   bool _shadow_caster;
   bool _has_specular_color;
   int _sb_sort;
 
+  PT(Texture) _shadow_map;
+
   // This is really a map of GSG -> GraphicsOutput.
   typedef pmap<PT(GraphicsStateGuardianBase), PT(GraphicsOutputBase) > ShadowBuffers;
   ShadowBuffers _sbuffers;
@@ -106,7 +111,6 @@ private:
   static TypeHandle _type_handle;
 
   friend class GraphicsStateGuardian;
-  friend class ShaderGenerator;
 };
 
 INLINE ostream &operator << (ostream &out, const LightLensNode &light) {

+ 30 - 0
panda/src/pgraphnodes/pointLight.cxx

@@ -17,6 +17,7 @@
 #include "bamReader.h"
 #include "datagram.h"
 #include "datagramIterator.h"
+#include "config_pgraphnodes.h"
 
 TypeHandle PointLight::_type_handle;
 
@@ -184,6 +185,35 @@ bind(GraphicsStateGuardianBase *gsg, const NodePath &light, int light_id) {
   gsg->bind_light(this, light, light_id);
 }
 
+/**
+ * Creates the shadow map texture.  Can be overridden.
+ */
+void PointLight::
+setup_shadow_map() {
+  if (_shadow_map != nullptr && _shadow_map->get_x_size() == _sb_size[0]) {
+    // Nothing to do.
+    return;
+  }
+
+  if (_sb_size[0] != _sb_size[1]) {
+    pgraphnodes_cat.error()
+      << "PointLight shadow buffers must have an equal width and height!\n";
+  }
+
+  if (_shadow_map == nullptr) {
+    _shadow_map = new Texture(get_name());
+  }
+
+  _shadow_map->setup_cube_map(_sb_size[0], Texture::T_unsigned_byte, Texture::F_depth_component);
+  _shadow_map->set_clear_color(LColor(1));
+  _shadow_map->set_wrap_u(SamplerState::WM_clamp);
+  _shadow_map->set_wrap_v(SamplerState::WM_clamp);
+
+  // Note: cube map shadow filtering doesn't seem to work in Cg.
+  _shadow_map->set_minfilter(SamplerState::FT_linear);
+  _shadow_map->set_magfilter(SamplerState::FT_linear);
+}
+
 /**
  * Tells the BamReader how to create objects of type PointLight.
  */

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

@@ -63,6 +63,8 @@ public:
                     int light_id);
 
 private:
+  virtual void setup_shadow_map();
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   public:

+ 0 - 7
panda/src/pipeline/cycleData.I

@@ -10,10 +10,3 @@
  * @author drose
  * @date 2002-02-21
  */
-
-/**
- *
- */
-INLINE CycleData::
-CycleData() {
-}

+ 3 - 1
panda/src/pipeline/cycleData.h

@@ -49,7 +49,9 @@ class EXPCL_PANDA_PIPELINE CycleData
 #endif  // DO_PIPELINING
 {
 public:
-  INLINE CycleData();
+  INLINE CycleData() DEFAULT_CTOR;
+  INLINE CycleData(CycleData &&from) DEFAULT_CTOR;
+  INLINE CycleData(const CycleData &copy) DEFAULT_CTOR;
   virtual ~CycleData();
 
   virtual CycleData *make_copy() const=0;

+ 2 - 2
panda/src/pipeline/pipeline.I

@@ -45,7 +45,7 @@ get_num_stages() const {
  */
 INLINE int Pipeline::
 get_num_cyclers() const {
-  ReMutexHolder holder(_lock);
+  MutexHolder holder(_lock);
   return _num_cyclers;
 }
 #endif  // THREADED_PIPELINE
@@ -58,7 +58,7 @@ get_num_cyclers() const {
  */
 INLINE int Pipeline::
 get_num_dirty_cyclers() const {
-  ReMutexHolder holder(_lock);
+  MutexHolder holder(_lock);
   return _num_dirty_cyclers;
 }
 #endif  // THREADED_PIPELINE

+ 189 - 67
panda/src/pipeline/pipeline.cxx

@@ -27,7 +27,9 @@ Pipeline(const string &name, int num_stages) :
   Namable(name),
 #ifdef THREADED_PIPELINE
   _num_stages(num_stages),
-  _lock("Pipeline")
+  _cycle_lock("Pipeline cycle"),
+  _lock("Pipeline"),
+  _next_cycle_seq(1)
 #else
   _num_stages(1)
 #endif
@@ -91,87 +93,166 @@ cycle() {
   }
 
   pvector< PT(CycleData) > saved_cdatas;
-  saved_cdatas.reserve(_num_dirty_cyclers);
   {
-    ReMutexHolder holder(_lock);
-    if (_num_stages == 1) {
-      // No need to cycle if there's only one stage.
-      nassertv(_dirty._next == &_dirty);
-      return;
-    }
+    ReMutexHolder cycle_holder(_cycle_lock);
+    int prev_seq, next_seq;
+    PipelineCyclerLinks prev_dirty;
+    {
+      // We can't hold the lock protecting the linked lists during the cycling
+      // itself, since it could cause a deadlock.
+      MutexHolder holder(_lock);
+      if (_num_stages == 1) {
+        // No need to cycle if there's only one stage.
+        nassertv(_dirty._next == &_dirty);
+        return;
+      }
 
-    nassertv(!_cycling);
-    _cycling = true;
+      nassertv(!_cycling);
+      _cycling = true;
 
-    // Move the dirty list to prev_dirty, for processing.
-    PipelineCyclerLinks prev_dirty;
-    prev_dirty.make_head();
-    prev_dirty.take_list(_dirty);
-    _num_dirty_cyclers = 0;
+      // Increment the cycle sequence number, which is used by this method to
+      // communicate with remove_cycler() about the status of dirty cyclers.
+      prev_seq = next_seq = _next_cycle_seq;
+      if (++next_seq == 0) {
+        // Skip 0, which is a reserved number used to indicate a clean cycler.
+        ++next_seq;
+      }
+      _next_cycle_seq = next_seq;
+
+      // Move the dirty list to prev_dirty, for processing.
+      prev_dirty.make_head();
+      prev_dirty.take_list(_dirty);
+
+      saved_cdatas.reserve(_num_dirty_cyclers);
+      _num_dirty_cyclers = 0;
+    }
 
+    // This is duplicated for different number of stages, as an optimization.
     switch (_num_stages) {
     case 2:
       while (prev_dirty._next != &prev_dirty) {
-        PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)prev_dirty._next;
-        cycler->remove_from_list();
-        ReMutexHolder holder2(cycler->_lock);
-
-        // We save the result of cycle(), so that we can defer the side-
-        // effects that might occur when CycleDatas destruct, at least until
-        // the end of this loop.
-        saved_cdatas.push_back(cycler->cycle_2());
-
-        if (cycler->_dirty) {
-          // The cycler is still dirty after cycling.  Keep it on the dirty
-          // list for next time.
-          cycler->insert_before(&_dirty);
-          ++_num_dirty_cyclers;
-        } else {
-          // The cycler is now clean.  Add it back to the clean list.
+        PipelineCyclerLinks *link = prev_dirty._next;
+        while (link != &prev_dirty) {
+          PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
+
+          if (!cycler->_lock.try_acquire()) {
+            // No big deal, just move on to the next one for now, and we'll
+            // come back around to it.  It's important not to block here in
+            // order to prevent one cycler from deadlocking another.
+            if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
+              link = cycler->_next;
+              continue;
+            } else {
+              // Well, we are the last cycler left, so we might as well wait.
+              // This is necessary to trigger the deadlock detection code.
+              cycler->_lock.acquire();
+            }
+          }
+
+          MutexHolder holder(_lock);
+          cycler->remove_from_list();
+
+          // We save the result of cycle(), so that we can defer the side-
+          // effects that might occur when CycleDatas destruct, at least until
+          // the end of this loop.
+          saved_cdatas.push_back(cycler->cycle_2());
+
+          // cycle_2() won't leave a cycler dirty.  Add it to the clean list.
+          nassertd(!cycler->_dirty) break;
           cycler->insert_before(&_clean);
 #ifdef DEBUG_THREADS
           inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
 #endif
+          cycler->_lock.release();
+          break;
         }
       }
       break;
 
     case 3:
       while (prev_dirty._next != &prev_dirty) {
-        PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)prev_dirty._next;
-        cycler->remove_from_list();
-        ReMutexHolder holder2(cycler->_lock);
-
-        saved_cdatas.push_back(cycler->cycle_3());
-
-        if (cycler->_dirty) {
-          cycler->insert_before(&_dirty);
-          ++_num_dirty_cyclers;
-        } else {
-          cycler->insert_before(&_clean);
+        PipelineCyclerLinks *link = prev_dirty._next;
+        while (link != &prev_dirty) {
+          PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
+
+          if (!cycler->_lock.try_acquire()) {
+            // No big deal, just move on to the next one for now, and we'll
+            // come back around to it.  It's important not to block here in
+            // order to prevent one cycler from deadlocking another.
+            if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
+              link = cycler->_next;
+              continue;
+            } else {
+              // Well, we are the last cycler left, so we might as well wait.
+              // This is necessary to trigger the deadlock detection code.
+              cycler->_lock.acquire();
+            }
+          }
+
+          MutexHolder holder(_lock);
+          cycler->remove_from_list();
+
+          saved_cdatas.push_back(cycler->cycle_3());
+
+          if (cycler->_dirty) {
+            // The cycler is still dirty.  Add it back to the dirty list.
+            nassertd(cycler->_dirty == prev_seq) break;
+            cycler->insert_before(&_dirty);
+            cycler->_dirty = next_seq;
+            ++_num_dirty_cyclers;
+          } else {
+            // The cycler is now clean.  Add it back to the clean list.
+            cycler->insert_before(&_clean);
 #ifdef DEBUG_THREADS
-          inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
+            inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
 #endif
+          }
+          cycler->_lock.release();
+          break;
         }
       }
       break;
 
     default:
       while (prev_dirty._next != &prev_dirty) {
-        PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)prev_dirty._next;
-        cycler->remove_from_list();
-        ReMutexHolder holder2(cycler->_lock);
-
-        saved_cdatas.push_back(cycler->cycle());
-
-        if (cycler->_dirty) {
-          cycler->insert_before(&_dirty);
-          ++_num_dirty_cyclers;
-        } else {
-          cycler->insert_before(&_clean);
+        PipelineCyclerLinks *link = prev_dirty._next;
+        while (link != &prev_dirty) {
+          PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
+
+          if (!cycler->_lock.try_acquire()) {
+            // No big deal, just move on to the next one for now, and we'll
+            // come back around to it.  It's important not to block here in
+            // order to prevent one cycler from deadlocking another.
+            if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
+              link = cycler->_next;
+              continue;
+            } else {
+              // Well, we are the last cycler left, so we might as well wait.
+              // This is necessary to trigger the deadlock detection code.
+              cycler->_lock.acquire();
+            }
+          }
+
+          MutexHolder holder(_lock);
+          cycler->remove_from_list();
+
+          saved_cdatas.push_back(cycler->cycle());
+
+          if (cycler->_dirty) {
+            // The cycler is still dirty.  Add it back to the dirty list.
+            nassertd(cycler->_dirty == prev_seq) break;
+            cycler->insert_before(&_dirty);
+            cycler->_dirty = next_seq;
+            ++_num_dirty_cyclers;
+          } else {
+            // The cycler is now clean.  Add it back to the clean list.
+            cycler->insert_before(&_clean);
 #ifdef DEBUG_THREADS
-          inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
+            inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
 #endif
+          }
+          cycler->_lock.release();
+          break;
         }
       }
       break;
@@ -203,7 +284,9 @@ void Pipeline::
 set_num_stages(int num_stages) {
   nassertv(num_stages >= 1);
 #ifdef THREADED_PIPELINE
-  ReMutexHolder holder(_lock);
+  // Make sure it's not currently cycling.
+  ReMutexHolder cycle_holder(_cycle_lock);
+  MutexHolder holder(_lock);
   if (num_stages != _num_stages) {
 
     // We need to lock every PipelineCycler object attached to this pipeline
@@ -261,9 +344,10 @@ set_num_stages(int num_stages) {
  */
 void Pipeline::
 add_cycler(PipelineCyclerTrueImpl *cycler) {
-  ReMutexHolder holder(_lock);
+  // It's safe to add it to the list while cycling, since the _clean list is
+  // not touched during the cycle loop.
+  MutexHolder holder(_lock);
   nassertv(!cycler->_dirty);
-  nassertv(!_cycling);
 
   cycler->insert_before(&_clean);
   ++_num_cyclers;
@@ -285,15 +369,16 @@ void Pipeline::
 add_dirty_cycler(PipelineCyclerTrueImpl *cycler) {
   nassertv(cycler->_lock.debug_is_locked());
 
-  ReMutexHolder holder(_lock);
-  nassertv(_num_stages != 1);
-  nassertv(!_cycling);
+  // It's safe to add it to the list while cycling, since it's not currently
+  // on the dirty list.
+  MutexHolder holder(_lock);
   nassertv(!cycler->_dirty);
+  nassertv(_num_stages != 1);
 
   // Remove it from the "clean" list and add it to the "dirty" list.
   cycler->remove_from_list();
   cycler->insert_before(&_dirty);
-  cycler->_dirty = true;
+  cycler->_dirty = _next_cycle_seq;
   ++_num_dirty_cyclers;
 
 #ifdef DEBUG_THREADS
@@ -311,9 +396,42 @@ void Pipeline::
 remove_cycler(PipelineCyclerTrueImpl *cycler) {
   nassertv(cycler->_lock.debug_is_locked());
 
-  ReMutexHolder holder(_lock);
-  nassertv(!_cycling);
+  MutexHolder holder(_lock);
+
+  // If it's dirty, it may currently be processed by cycle(), so we need to be
+  // careful not to cause a race condition.  It's safe for us to remove it
+  // during cycle only if it's 0 (clean) or _next_cycle_seq (scheduled for the
+  // next cycle, so not owned by the current one).
+  while (cycler->_dirty != 0 && cycler->_dirty != _next_cycle_seq) {
+    if (_cycle_lock.try_acquire()) {
+      // OK, great, we got the lock, so it finished cycling already.
+      nassertv(!_cycling);
+
+      --_num_cyclers;
+      cycler->remove_from_list();
+
+      cycler->_dirty = false;
+      --_num_dirty_cyclers;
+
+  #ifdef DEBUG_THREADS
+      inc_cycler_type(_all_cycler_types, cycler->get_parent_type(), -1);
+      inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
+  #endif
+
+      _cycle_lock.release();
+      return;
+    } else {
+      // It's possibly currently being cycled.  We will wait for the cycler
+      // to be done with it, so that we can safely remove it.
+      _lock.release();
+      cycler->_lock.release();
+      Thread::force_yield();
+      cycler->_lock.acquire();
+      _lock.acquire();
+    }
+  }
 
+  // It's not being owned by a cycle operation, so it's fair game.
   --_num_cyclers;
   cycler->remove_from_list();
 
@@ -322,7 +440,7 @@ remove_cycler(PipelineCyclerTrueImpl *cycler) {
 #endif
 
   if (cycler->_dirty) {
-    cycler->_dirty = false;
+    cycler->_dirty = 0;
     --_num_dirty_cyclers;
 #ifdef DEBUG_THREADS
     inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
@@ -341,7 +459,9 @@ remove_cycler(PipelineCyclerTrueImpl *cycler) {
  */
 void Pipeline::
 iterate_all_cycler_types(CallbackFunc *func, void *data) const {
-  ReMutexHolder holder(_lock);
+  // Make sure it's not currently cycling.
+  ReMutexHolder cycle_holder(_cycle_lock);
+  MutexHolder holder(_lock);
   TypeCount::const_iterator ci;
   for (ci = _all_cycler_types.begin(); ci != _all_cycler_types.end(); ++ci) {
     func((*ci).first, (*ci).second, data);
@@ -356,7 +476,9 @@ iterate_all_cycler_types(CallbackFunc *func, void *data) const {
  */
 void Pipeline::
 iterate_dirty_cycler_types(CallbackFunc *func, void *data) const {
-  ReMutexHolder holder(_lock);
+  // Make sure it's not currently cycling.
+  ReMutexHolder cycle_holder(_cycle_lock);
+  MutexHolder holder(_lock);
   TypeCount::const_iterator ci;
   for (ci = _dirty_cycler_types.begin(); ci != _dirty_cycler_types.end(); ++ci) {
     func((*ci).first, (*ci).second, data);

+ 10 - 1
panda/src/pipeline/pipeline.h

@@ -85,7 +85,16 @@ private:
   // This is true only during cycle().
   bool _cycling;
 
-  ReMutex _lock;
+  // This increases with every cycle run.  If the _dirty field of a cycler is
+  // set to the same value as this, it indicates that it is scheduled for the
+  // next cycle.
+  unsigned int _next_cycle_seq;
+
+  // This lock is always held during cycle().
+  ReMutex _cycle_lock;
+
+  // This lock protects the data stored on this Pipeline.
+  Mutex _lock;
 #endif  // THREADED_PIPELINE
 };
 

+ 21 - 0
panda/src/pipeline/pipelineCycler.I

@@ -25,6 +25,16 @@ PipelineCycler(Pipeline *pipeline) :
 {
 }
 
+/**
+ *
+ */
+template<class CycleDataType>
+INLINE PipelineCycler<CycleDataType>::
+PipelineCycler(CycleDataType &&initial_data, Pipeline *pipeline) :
+  PipelineCyclerBase(new CycleDataType(move(initial_data)), pipeline)
+{
+}
+
 /**
  *
  */
@@ -182,6 +192,17 @@ PipelineCycler(Pipeline *pipeline) :
 {
 }
 
+/**
+ *
+ */
+template<class CycleDataType>
+INLINE PipelineCycler<CycleDataType>::
+PipelineCycler(CycleDataType &&initial_data, Pipeline *pipeline) :
+  _typed_data(move(initial_data)),
+  PipelineCyclerBase(&_typed_data, pipeline)
+{
+}
+
 /**
  *
  */

+ 3 - 1
panda/src/pipeline/pipelineCycler.h

@@ -45,7 +45,9 @@
 template<class CycleDataType>
 struct PipelineCycler : public PipelineCyclerBase {
 public:
-  INLINE PipelineCycler(Pipeline *pipeline = NULL);
+  INLINE PipelineCycler(Pipeline *pipeline = nullptr);
+  INLINE PipelineCycler(CycleDataType &&initial_data, Pipeline *pipeline = nullptr);
+
   INLINE PipelineCycler(const PipelineCycler<CycleDataType> &copy);
   INLINE void operator = (const PipelineCycler<CycleDataType> &copy);
 

+ 2 - 2
panda/src/pipeline/pipelineCyclerTrueImpl.I

@@ -393,7 +393,7 @@ cycle_2() {
   _data[1]._cdata = _data[0]._cdata;
 
   // No longer dirty.
-  _dirty = false;
+  _dirty = 0;
   return last_val;
 }
 
@@ -423,7 +423,7 @@ cycle_3() {
 
   if (_data[2]._cdata == _data[1]._cdata) {
     // No longer dirty.
-    _dirty = false;
+    _dirty = 0;
   }
 
   return last_val;

+ 3 - 3
panda/src/pipeline/pipelineCyclerTrueImpl.cxx

@@ -24,7 +24,7 @@
 PipelineCyclerTrueImpl::
 PipelineCyclerTrueImpl(CycleData *initial_data, Pipeline *pipeline) :
   _pipeline(pipeline),
-  _dirty(false),
+  _dirty(0),
   _lock(this)
 {
   if (_pipeline == (Pipeline *)NULL) {
@@ -46,7 +46,7 @@ PipelineCyclerTrueImpl(CycleData *initial_data, Pipeline *pipeline) :
 PipelineCyclerTrueImpl::
 PipelineCyclerTrueImpl(const PipelineCyclerTrueImpl &copy) :
   _pipeline(copy._pipeline),
-  _dirty(false),
+  _dirty(0),
   _lock(this)
 {
   ReMutexHolder holder(_lock);
@@ -278,7 +278,7 @@ cycle() {
   }
 
   // No longer dirty.
-  _dirty = false;
+  _dirty = 0;
   return last_val;
 }
 

+ 4 - 1
panda/src/pipeline/pipelineCyclerTrueImpl.h

@@ -122,7 +122,10 @@ private:
   };
   CycleDataNode *_data;
   int _num_stages;
-  bool _dirty;
+
+  // This is 0 if it's clean, or set to Pipeline::_next_cycle_seq if it's
+  // scheduled to be cycled during the next cycle() call.
+  unsigned int _dirty;
 
   CyclerMutex _lock;
 

+ 14 - 0
panda/src/pnmimage/convert_srgb.I

@@ -153,3 +153,17 @@ encode_sRGB_uchar(const LColorf &color, xel &into, xelval &into_alpha) {
   into_alpha = (xelval) (color[3] * 255.f + 0.5f);
 #endif
 }
+
+
+/**
+ * Double-precision versions of the above.
+ */
+INLINE void
+encode_sRGB_uchar(const LColord &color, xel &into) {
+  return encode_sRGB_uchar(LCAST(float, color), into);
+}
+
+INLINE void
+encode_sRGB_uchar(const LColord &color, xel &into, xelval &into_alpha) {
+  return encode_sRGB_uchar(LCAST(float, color), into, into_alpha);
+}

+ 5 - 0
panda/src/pnmimage/convert_srgb.h

@@ -46,6 +46,11 @@ EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from,
 EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from,
                                                    xel &into, xelval &into_alpha);
 
+EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColord &from,
+                                                   xel &into);
+EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColord &from,
+                                                   xel &into, xelval &into_alpha);
+
 // Use these functions if you know that SSE2 support is available.  Otherwise,
 // they will crash!
 #if defined(__SSE2__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64)

+ 1 - 1
panda/src/pnmimage/pnmImage.I

@@ -80,7 +80,7 @@ to_val(float input_value) const {
   switch (_xel_encoding) {
   case XE_generic:
   case XE_generic_alpha:
-    return clamp_val((int)(input_value * get_maxval() + 0.5f));
+    return (int)(min(1.0f, max(0.0f, input_value)) * get_maxval() + 0.5f);
 
   case XE_generic_sRGB:
   case XE_generic_sRGB_alpha:

+ 4 - 2
panda/src/putil/clockObject.I

@@ -213,10 +213,12 @@ check_errors(Thread *current_thread) {
  */
 INLINE ClockObject *ClockObject::
 get_global_clock() {
-  if (_global_clock == (ClockObject *)NULL) {
+  ClockObject *clock = (ClockObject *)AtomicAdjust::get_ptr(_global_clock);
+  if (UNLIKELY(clock == nullptr)) {
     make_global_clock();
+    clock = (ClockObject *)_global_clock;
   }
-  return _global_clock;
+  return clock;
 }
 
 /**

+ 10 - 11
panda/src/putil/clockObject.cxx

@@ -21,21 +21,16 @@ void (*ClockObject::_start_clock_wait)() = ClockObject::dummy_clock_wait;
 void (*ClockObject::_start_clock_busy_wait)() = ClockObject::dummy_clock_wait;
 void (*ClockObject::_stop_clock_wait)() = ClockObject::dummy_clock_wait;
 
-ClockObject *ClockObject::_global_clock;
+AtomicAdjust::Pointer ClockObject::_global_clock = nullptr;
 TypeHandle ClockObject::_type_handle;
 
 /**
  *
  */
 ClockObject::
-ClockObject() : _ticks(get_class_type()) {
+ClockObject(Mode mode) : _ticks(get_class_type()), _mode(mode) {
   _true_clock = TrueClock::get_global_ptr();
 
-  // Each clock except for the application global clock is created in M_normal
-  // mode.  The application global clock is later reset to respect clock_mode,
-  // which comes from the Config.prc file.
-  _mode = M_normal;
-
   _start_short_time = _true_clock->get_short_time();
   _start_long_time = _true_clock->get_long_time();
   _actual_frame_time = 0.0;
@@ -523,7 +518,7 @@ wait_until(double want_time) {
  */
 void ClockObject::
 make_global_clock() {
-  nassertv(_global_clock == (ClockObject *)NULL);
+  nassertv(_global_clock == nullptr);
 
   ConfigVariableEnum<ClockObject::Mode> clock_mode
     ("clock-mode", ClockObject::M_normal,
@@ -532,9 +527,13 @@ make_global_clock() {
               "effects like simulated reduced frame rate.  See "
               "ClockObject::set_mode()."));
 
-  _global_clock = new ClockObject;
-  _global_clock->set_mode(clock_mode);
-  _global_clock->ref();
+  ClockObject *clock = new ClockObject(clock_mode);
+  clock->local_object();
+
+  if (AtomicAdjust::compare_and_exchange_ptr(_global_clock, nullptr, clock) != nullptr) {
+    // Another thread beat us to it.
+    delete clock;
+  }
 }
 
 /**

+ 2 - 2
panda/src/putil/clockObject.h

@@ -68,7 +68,7 @@ PUBLISHED:
     M_integer_limited,
   };
 
-  ClockObject();
+  ClockObject(Mode mode = M_normal);
   ClockObject(const ClockObject &copy);
   INLINE ~ClockObject();
 
@@ -172,7 +172,7 @@ private:
   typedef CycleDataWriter<CData> CDWriter;
   typedef CycleDataStageReader<CData> CDStageReader;
 
-  static ClockObject *_global_clock;
+  static AtomicAdjust::Pointer _global_clock;
 
 public:
   static TypeHandle get_class_type() {

+ 13 - 2
panda/src/putil/simpleHashMap.I

@@ -41,7 +41,13 @@ SimpleHashMap(const SimpleHashMap &copy) :
 
   _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
   _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
-  memcpy(_table, copy._table, alloc_size);
+
+  for (size_t i = 0; i < _num_entries; ++i) {
+    new(&_table[i]) TableEntry(copy._table[i]);
+  }
+
+  // Copy the index array.
+  memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity);
 }
 
 /**
@@ -88,7 +94,12 @@ operator = (const SimpleHashMap<Key, Value, Compare> &copy) {
 
     _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
     _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
-    memcpy(_table, copy._table, alloc_size);
+    for (size_t i = 0; i < _num_entries; ++i) {
+      new(&_table[i]) TableEntry(copy._table[i]);
+    }
+
+    // Copy the index array.
+    memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity);
   }
   return *this;
 }

+ 1 - 1
panda/src/wgldisplay/wglGraphicsWindow.cxx

@@ -386,7 +386,7 @@ print_pfd(PIXELFORMATDESCRIPTOR *pfd, char *msg) {
 
   wgldisplay_cat.debug()
     << msg << ", " << OGLDrvStrings[drvtype] << " driver\n"
-    << "PFD flags: 0x" << (void*)pfd->dwFlags << " ("
+    << "PFD flags: 0x" << hex << pfd->dwFlags << dec << " ("
     << PRINT_FLAG(GENERIC_ACCELERATED)
     << PRINT_FLAG(GENERIC_FORMAT)
     << PRINT_FLAG(DOUBLEBUFFER)

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff