Browse Source

Merge branch 'master' into shaderpipeline

rdb 4 years ago
parent
commit
863c1c495d

+ 10 - 10
.github/workflows/ci.yml

@@ -92,10 +92,10 @@ jobs:
     - name: Install dependencies (macOS)
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
-        tar -xf panda3d-1.10.8-tools-mac.tar.gz
-        mv panda3d-1.10.8/thirdparty thirdparty
-        rmdir panda3d-1.10.8
+        curl -O https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz
+        tar -xf panda3d-1.10.9-tools-mac.tar.gz
+        mv panda3d-1.10.9/thirdparty thirdparty
+        rmdir panda3d-1.10.9
 
 
         # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
         # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
         install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
         install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
@@ -347,16 +347,16 @@ jobs:
       shell: powershell
       shell: powershell
       run: |
       run: |
         $wc = New-Object System.Net.WebClient
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.9/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
-        tar -xf panda3d-1.10.8-tools-mac.tar.gz
-        mv panda3d-1.10.8/thirdparty thirdparty
-        rmdir panda3d-1.10.8
+        curl -O https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz
+        tar -xf panda3d-1.10.9-tools-mac.tar.gz
+        mv panda3d-1.10.9/thirdparty thirdparty
+        rmdir panda3d-1.10.9
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
     - name: Set up Python 3.9
     - name: Set up Python 3.9
       uses: actions/setup-python@v1
       uses: actions/setup-python@v1

+ 0 - 7
.travis.yml

@@ -1,7 +0,0 @@
-language: cpp
-branches:
-  only:
-    - release/1.10.x
-    - release/1.9.x
-script:
-    - echo "Build disabled on master branch."

+ 1 - 1
direct/src/distributed/ClockDelta.py

@@ -1,7 +1,7 @@
 # ClockDelta provides the ability to use clock synchronization for
 # ClockDelta provides the ability to use clock synchronization for
 # distributed objects
 # distributed objects
 
 
-from panda3d.core import ClockObject
+from panda3d.core import ClockObject, ConfigVariableBool
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
 import math
 import math

+ 1 - 2
direct/src/gui/DirectSlider.py

@@ -58,8 +58,7 @@ class DirectSlider(DirectFrame):
         self.thumb = self.createcomponent("thumb", (), None,
         self.thumb = self.createcomponent("thumb", (), None,
                                           DirectButton, (self,),
                                           DirectButton, (self,),
                                           borderWidth = self['borderWidth'])
                                           borderWidth = self['borderWidth'])
-        if self.thumb['frameSize'] is None and \
-           self.thumb.bounds == [0.0, 0.0, 0.0, 0.0]:
+        if self.thumb['frameSize'] is None:
             # Compute a default frameSize for the thumb.
             # Compute a default frameSize for the thumb.
             f = self['frameSize']
             f = self['frameSize']
             if self['orientation'] == DGG.HORIZONTAL:
             if self['orientation'] == DGG.HORIZONTAL:

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

@@ -91,6 +91,7 @@ pause = AsyncTaskPause
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 
 
 gather = Task.gather
 gather = Task.gather
+shield = Task.shield
 
 
 def sequence(*taskList):
 def sequence(*taskList):
     seq = AsyncTaskSequence('sequence')
     seq = AsyncTaskSequence('sequence')

+ 4 - 0
dtool/src/dtoolutil/dSearchPath.cxx

@@ -242,6 +242,10 @@ get_directory(size_t n) const {
  */
  */
 Filename DSearchPath::
 Filename DSearchPath::
 find_file(const Filename &filename) const {
 find_file(const Filename &filename) const {
+  if (filename.empty()) {
+    return string();
+  }
+
   if (filename.is_local()) {
   if (filename.is_local()) {
     if (_directories.empty()) {
     if (_directories.empty()) {
       // Let's say an empty search path is the same as a search path
       // Let's say an empty search path is the same as a search path

+ 1 - 1
dtool/src/dtoolutil/pandaFileStreamBuf.cxx

@@ -333,7 +333,7 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
       // Posix case.
       // Posix case.
       {
       {
         off_t li = lseek(_fd, off, SEEK_END);
         off_t li = lseek(_fd, off, SEEK_END);
-        if (li == (off_t)-1) {
+        if (li == (off_t)-1 || (sizeof(off_t) == 8 && li == 0x7fffffffffffffff)) {
           return -1;
           return -1;
         }
         }
         new_pos = (size_t)li;
         new_pos = (size_t)li;

+ 14 - 49
dtool/src/interrogate/functionRemap.cxx

@@ -419,6 +419,17 @@ get_call_str(const string &container, const vector_string &pexprs) const {
       call << ')';
       call << ')';
     }
     }
 
 
+  } else if (_type == T_item_assignment_operator) {
+    call << "(";
+    _parameters[0]._remap->pass_parameter(call, container);
+    call << ")[";
+
+    size_t pn = _first_true_parameter;
+    _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
+    call << "] = ";
+    ++pn;
+    _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
+
   } else {
   } else {
     const char *separator = "";
     const char *separator = "";
 
 
@@ -468,11 +479,6 @@ get_call_str(const string &container, const vector_string &pexprs) const {
     size_t pn = _first_true_parameter;
     size_t pn = _first_true_parameter;
     size_t num_parameters = pexprs.size();
     size_t num_parameters = pexprs.size();
 
 
-    if (_type == T_item_assignment_operator) {
-      // The last parameter is the value to set.
-      --num_parameters;
-    }
-
     for (pn = _first_true_parameter;
     for (pn = _first_true_parameter;
          pn < num_parameters; ++pn) {
          pn < num_parameters; ++pn) {
       nassertd(pn < _parameters.size()) break;
       nassertd(pn < _parameters.size()) break;
@@ -481,11 +487,6 @@ get_call_str(const string &container, const vector_string &pexprs) const {
       separator = ", ";
       separator = ", ";
     }
     }
     call << ")";
     call << ")";
-
-    if (_type == T_item_assignment_operator) {
-      call << " = ";
-      _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
-    }
   }
   }
 
 
   return call.str();
   return call.str();
@@ -568,6 +569,9 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
 
 
   } else if ((ifunc._flags & InterrogateFunction::F_setter) != 0) {
   } else if ((ifunc._flags & InterrogateFunction::F_setter) != 0) {
     _type = T_setter;
     _type = T_setter;
+
+  } else if ((ifunc._flags & InterrogateFunction::F_item_assignment) != 0) {
+    _type = T_item_assignment_operator;
   }
   }
 
 
   if ((_cppfunc->_storage_class & CPPInstance::SC_blocking) != 0) {
   if ((_cppfunc->_storage_class & CPPInstance::SC_blocking) != 0) {
@@ -623,14 +627,6 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
         fname == "operator <<=" ||
         fname == "operator <<=" ||
         fname == "operator >>=") {
         fname == "operator >>=") {
       _type = T_assignment_method;
       _type = T_assignment_method;
-
-    } else if (fname == "operator []" && !_const_method && rtype != nullptr) {
-       // Check if this is an item-assignment operator.
-      CPPReferenceType *reftype = rtype->as_reference_type();
-      if (reftype != nullptr && reftype->_pointing_at->as_const_type() == nullptr) {
-        // It returns a mutable reference.
-        _type = T_item_assignment_operator;
-      }
     }
     }
   }
   }
 
 
@@ -707,37 +703,6 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
       }
       }
     }
     }
 
 
-  } else if (_type == T_item_assignment_operator) {
-    // An item-assignment method isn't really a thing in C++, but it is in
-    // scripting languages, so we use this to denote item-access operators
-    // that return a non-const reference.
-
-    if (_cpptype == nullptr) {
-      nout << "Method " << *_cppfunc << " has no struct type\n";
-      return false;
-    } else {
-      // Synthesize a const reference parameter for the assignment.
-      CPPType *bare_type = TypeManager::unwrap_reference(rtype);
-      CPPType *const_type = CPPType::new_type(new CPPConstType(bare_type));
-      CPPType *ref_type = CPPType::new_type(new CPPReferenceType(const_type));
-
-      Parameter param;
-      param._has_name = true;
-      param._name = "assign_val";
-      param._remap = interface_maker->remap_parameter(_cpptype, ref_type);
-
-      if (param._remap == nullptr || !param._remap->is_valid()) {
-        nout << "Invalid remap for assignment type of method " << *_cppfunc << "\n";
-        return false;
-      }
-      _parameters.push_back(param);
-
-      // Pretend we don't return anything at all.
-      CPPType *void_type = TypeManager::get_void_type();
-      _return_type = interface_maker->remap_parameter(_cpptype, void_type);
-      _void_return = true;
-    }
-
   } else if (fname == "operator <=>") {
   } else if (fname == "operator <=>") {
     // This returns an opaque object that we must leave unchanged.
     // This returns an opaque object that we must leave unchanged.
     _return_type = new ParameterRemapUnchanged(rtype);
     _return_type = new ParameterRemapUnchanged(rtype);

+ 1 - 0
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -74,6 +74,7 @@ RenameSet methodRenameDictionary[] = {
   { "operator ="    , "assign",                 0 },
   { "operator ="    , "assign",                 0 },
   { "operator ()"   , "__call__",               0 },
   { "operator ()"   , "__call__",               0 },
   { "operator []"   , "__getitem__",            0 },
   { "operator []"   , "__getitem__",            0 },
+  { "operator [] =" , "__setitem__",            0 },
   { "operator ++unary", "increment",            0 },
   { "operator ++unary", "increment",            0 },
   { "operator ++"   , "increment",              0 },
   { "operator ++"   , "increment",              0 },
   { "operator --unary", "decrement",            0 },
   { "operator --unary", "decrement",            0 },

+ 32 - 0
dtool/src/interrogate/interrogateBuilder.cxx

@@ -3012,6 +3012,38 @@ define_method(CPPInstance *function, InterrogateType &itype,
                index) == itype._methods.end()) {
                index) == itype._methods.end()) {
         itype._methods.push_back(index);
         itype._methods.push_back(index);
       }
       }
+
+      // For an operator [] returning a non-const reference, we synthesize an
+      // "item-assignment" operator, which does not exist in C++ but does in
+      // scripting languages.  This allows `obj[n] = ...`
+      if (ftype->_return_type != nullptr &&
+          ftype->_return_type->is_reference() &&
+          !ftype->_return_type->remove_reference()->is_const() &&
+          (ftype->_flags & CPPFunctionType::F_const_method) == 0 &&
+          function->get_simple_name() == "operator []") {
+
+        // Make up a CPPFunctionType with extra parameter.
+        CPPType *assign_type = TypeManager::wrap_const_reference(ftype->_return_type->remove_reference());
+        CPPParameterList *params = new CPPParameterList(*(ftype->_parameters));
+        CPPInstance *param1 = new CPPInstance(assign_type, "assign_val");
+        params->_parameters.push_back(param1);
+        CPPType *void_type = TypeManager::get_void_type();
+        CPPFunctionType *ftype = new CPPFunctionType(void_type, params, 0);
+
+        // Now make up an instance for the function.
+        CPPInstance *function = new CPPInstance(ftype, "operator [] =");
+        function->_ident->_native_scope = scope;
+
+        FunctionIndex index = get_function(function, "",
+                                           struct_type, scope,
+                                           InterrogateFunction::F_item_assignment);
+        if (index != 0) {
+          if (find(itype._methods.begin(), itype._methods.end(),
+                   index) == itype._methods.end()) {
+            itype._methods.push_back(index);
+          }
+        }
+      }
     }
     }
   }
   }
 }
 }

+ 1 - 0
dtool/src/interrogatedb/interrogateFunction.h

@@ -70,6 +70,7 @@ private:
     F_setter          = 0x0020,
     F_setter          = 0x0020,
     F_unary_op        = 0x0040,
     F_unary_op        = 0x0040,
     F_operator_typecast = 0x0080,
     F_operator_typecast = 0x0080,
+    F_item_assignment = 0x0100,
   };
   };
 
 
   int _flags;
   int _flags;

+ 2 - 2
makepanda/makepanda.py

@@ -820,6 +820,7 @@ if (COMPILER=="GCC"):
         if (os.path.isdir("/usr/PCBSD")):
         if (os.path.isdir("/usr/PCBSD")):
             IncDirectory("ALWAYS", "/usr/PCBSD/local/include")
             IncDirectory("ALWAYS", "/usr/PCBSD/local/include")
             LibDirectory("ALWAYS", "/usr/PCBSD/local/lib")
             LibDirectory("ALWAYS", "/usr/PCBSD/local/lib")
+        SmartPkgEnable("INOTIFY", "libinotify", ("inotify"), "sys/inotify.h")
 
 
     if GetTarget() != "windows":
     if GetTarget() != "windows":
         PkgDisable("DIRECTCAM")
         PkgDisable("DIRECTCAM")
@@ -2470,7 +2471,6 @@ def WriteConfigSettings():
         dtool_config["IS_FREEBSD"] = '1'
         dtool_config["IS_FREEBSD"] = '1'
         dtool_config["PHAVE_ALLOCA_H"] = 'UNDEF'
         dtool_config["PHAVE_ALLOCA_H"] = 'UNDEF'
         dtool_config["PHAVE_MALLOC_H"] = 'UNDEF'
         dtool_config["PHAVE_MALLOC_H"] = 'UNDEF'
-        dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF'
         dtool_config["HAVE_PROC_CURPROC_FILE"] = '1'
         dtool_config["HAVE_PROC_CURPROC_FILE"] = '1'
         dtool_config["HAVE_PROC_CURPROC_MAP"] = '1'
         dtool_config["HAVE_PROC_CURPROC_MAP"] = '1'
         dtool_config["HAVE_PROC_CURPROC_CMDLINE"] = '1'
         dtool_config["HAVE_PROC_CURPROC_CMDLINE"] = '1'
@@ -4042,7 +4042,7 @@ TargetAdd('libp3dxml.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dxml', 'SRCDIR:p
 OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
 OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
     'TIFF', 'OPENEXR', 'ZLIB', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
     'TIFF', 'OPENEXR', 'ZLIB', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
     'SQUISH', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
     'SQUISH', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
-    'SETUPAPI', 'IOKIT', 'GLSLANG', 'SPIRV-TOOLS']
+    'SETUPAPI', 'INOTIFY', 'IOKIT', 'GLSLANG', 'SPIRV-TOOLS']
 
 
 TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 
 

+ 18 - 1
panda/src/device/evdevInputDevice.cxx

@@ -328,7 +328,22 @@ init_device() {
   uint8_t axes[(ABS_MAX + 8) >> 3] = {0};
   uint8_t axes[(ABS_MAX + 8) >> 3] = {0};
   if (test_bit(EV_ABS, evtypes)) {
   if (test_bit(EV_ABS, evtypes)) {
     // Check which axes are on the device.
     // Check which axes are on the device.
-    num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
+    int result = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes);
+#ifdef __FreeBSD__
+    // Older kernels had a bug where this would always return 0, see D28218
+    if (result == 0) {
+      for (int i = ABS_MAX; i >= 0; --i) {
+        if (test_bit(i, axes)) {
+          num_bits = i + 1;
+          break;
+        }
+      }
+    }
+    else
+#endif
+    if (result > 0) {
+      num_bits = result << 3;
+    }
     has_axes = true;
     has_axes = true;
   }
   }
 
 
@@ -691,6 +706,7 @@ init_device() {
     _rtrigger_code = -1;
     _rtrigger_code = -1;
   }
   }
 
 
+#ifndef __FreeBSD__
   char path[64];
   char path[64];
   char buffer[256];
   char buffer[256];
   const char *parent = "";
   const char *parent = "";
@@ -728,6 +744,7 @@ init_device() {
     }
     }
     fclose(f);
     fclose(f);
   }
   }
+#endif
 
 
   // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel
   // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel
   // driver always reports 4 connected gamepads, regardless of the number
   // driver always reports 4 connected gamepads, regardless of the number

+ 3 - 0
panda/src/device/linuxInputDeviceManager.cxx

@@ -134,6 +134,7 @@ consider_add_evdev_device(size_t ev_index) {
   // having read permissions set, but doesn't export all of the features
   // having read permissions set, but doesn't export all of the features
   // (notably, force feedback).
   // (notably, force feedback).
 
 
+#ifndef __FreeBSD__
   // We do this by checking for a js# directory inside the sysfs directory.
   // We do this by checking for a js# directory inside the sysfs directory.
   sprintf(path, "/sys/class/input/event%zd/device", ev_index);
   sprintf(path, "/sys/class/input/event%zd/device", ev_index);
 
 
@@ -168,6 +169,8 @@ consider_add_evdev_device(size_t ev_index) {
   }
   }
 
 
   closedir(dir);
   closedir(dir);
+#endif
+
   return nullptr;
   return nullptr;
 }
 }
 
 

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

@@ -359,6 +359,12 @@ add_render_texture(Texture *tex, RenderTextureMode mode,
     // If we're still planning on binding, indicate it in texture properly.
     // If we're still planning on binding, indicate it in texture properly.
     tex->set_render_to_texture(true);
     tex->set_render_to_texture(true);
   }
   }
+  else if ((plane == RTP_depth || plane == RTP_depth_stencil) && _fb_properties.get_depth_bits() == 0) {
+    // If we're not providing the depth buffer, we need something to copy from.
+    display_cat.error()
+      << "add_render_texture: can't copy depth from framebuffer without depth bits!\n";
+    return;
+  }
 
 
   CDWriter cdata(_cycler, true);
   CDWriter cdata(_cycler, true);
   RenderTexture result;
   RenderTexture result;

+ 18 - 0
panda/src/event/asyncFuture.I

@@ -133,6 +133,24 @@ gather(Futures futures) {
   }
   }
 }
 }
 
 
+/**
+ * Creates a new future that shields the given future from cancellation.
+ * Calling `cancel()` on the returned future will not affect the given future.
+ */
+INLINE PT(AsyncFuture) AsyncFuture::
+shield(PT(AsyncFuture) future) {
+  if (future->try_lock_pending()) {
+    PT(AsyncFuture) outer = new AsyncFuture;
+    outer->_manager = future->_manager;
+    future->_waiting.push_back((AsyncFuture *)outer);
+    future->unlock();
+    return outer;
+  }
+  else {
+    return future;
+  }
+}
+
 /**
 /**
  * Tries to atomically lock the future, assuming it is pending.  Returns false
  * 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
  * if it is not in the pending state, implying it's either done or about to be

+ 21 - 4
panda/src/event/asyncFuture.cxx

@@ -148,13 +148,13 @@ notify_done(bool clean_exit) {
   // This will only be called by the thread that managed to set the
   // 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.
   // _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;
+  // Go through the futures that are waiting for this to finish.
+  for (AsyncFuture *fut : _waiting) {
     if (fut->is_task()) {
     if (fut->is_task()) {
       // It's a task.  Make it active again.
       // It's a task.  Make it active again.
       wake_task((AsyncTask *)fut);
       wake_task((AsyncTask *)fut);
-    } else {
+    }
+    else if (fut->get_type() == AsyncGatheringFuture::get_class_type()) {
       // It's a gathering future.  Decrease the pending count on it, and if
       // It's a gathering future.  Decrease the pending count on it, and if
       // we're the last one, call notify_done() on it.
       // we're the last one, call notify_done() on it.
       AsyncGatheringFuture *gather = (AsyncGatheringFuture *)fut;
       AsyncGatheringFuture *gather = (AsyncGatheringFuture *)fut;
@@ -164,6 +164,23 @@ notify_done(bool clean_exit) {
         }
         }
       }
       }
     }
     }
+    else {
+      // It's a shielding future.  The shielding only protects the inner future
+      // when the outer is cancelled, not the other way around, so we have to
+      // propagate any cancellation here as well.
+      if (clean_exit && _result != nullptr) {
+        // Propagate the result, if any.
+        if (fut->try_lock_pending()) {
+          fut->_result = _result;
+          fut->_result_ref = _result_ref;
+          fut->unlock(FS_finished);
+          fut->notify_done(true);
+        }
+      }
+      else if (fut->set_future_state(clean_exit ? FS_finished : FS_cancelled)) {
+        fut->notify_done(clean_exit);
+      }
+    }
   }
   }
   _waiting.clear();
   _waiting.clear();
 
 

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

@@ -67,7 +67,7 @@ PUBLISHED:
 
 
   INLINE bool done() const;
   INLINE bool done() const;
   INLINE bool cancelled() const;
   INLINE bool cancelled() const;
-  EXTENSION(PyObject *result(PyObject *timeout = Py_None) const);
+  EXTENSION(PyObject *result(PyObject *self, PyObject *timeout = Py_None) const);
 
 
   virtual bool cancel();
   virtual bool cancel();
 
 
@@ -78,6 +78,7 @@ PUBLISHED:
   EXTENSION(PyObject *add_done_callback(PyObject *self, PyObject *fn));
   EXTENSION(PyObject *add_done_callback(PyObject *self, PyObject *fn));
 
 
   EXTENSION(static PyObject *gather(PyObject *args));
   EXTENSION(static PyObject *gather(PyObject *args));
+  INLINE static PT(AsyncFuture) shield(PT(AsyncFuture) future);
 
 
   virtual void output(std::ostream &out) const;
   virtual void output(std::ostream &out) const;
 
 

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

@@ -222,16 +222,35 @@ set_result(PyObject *result) {
  * raises TimeoutError.
  * raises TimeoutError.
  */
  */
 PyObject *Extension<AsyncFuture>::
 PyObject *Extension<AsyncFuture>::
-result(PyObject *timeout) const {
+result(PyObject *self, PyObject *timeout) const {
+  double timeout_val;
+  if (timeout != Py_None) {
+    timeout_val = PyFloat_AsDouble(timeout);
+    if (timeout_val == -1.0 && _PyErr_OCCURRED()) {
+      return nullptr;
+    }
+  }
+
   if (!_this->done()) {
   if (!_this->done()) {
     // Not yet done?  Wait until it is done, or until a timeout occurs.  But
     // 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.
     // first check to make sure we're not trying to deadlock the thread.
     Thread *current_thread = Thread::get_current_thread();
     Thread *current_thread = Thread::get_current_thread();
-    if (_this == (const AsyncFuture *)current_thread->get_current_task()) {
+    AsyncTask *current_task = (AsyncTask *)current_thread->get_current_task();
+    if (_this == current_task) {
       PyErr_SetString(PyExc_RuntimeError, "cannot call task.result() from within the task");
       PyErr_SetString(PyExc_RuntimeError, "cannot call task.result() from within the task");
       return nullptr;
       return nullptr;
     }
     }
 
 
+    PythonTask *python_task = nullptr;
+    if (current_task != nullptr &&
+        current_task->is_of_type(PythonTask::get_class_type())) {
+      // If we are calling result() inside a coroutine, mark it as awaiting this
+      // future.  That makes it possible to cancel() us from another thread.
+      python_task = (PythonTask *)current_task;
+      nassertr(python_task->_fut_waiter == nullptr, nullptr);
+      python_task->_fut_waiter = self;
+    }
+
     // Release the GIL for the duration.
     // Release the GIL for the duration.
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
     PyThreadState *_save;
     PyThreadState *_save;
@@ -239,18 +258,18 @@ result(PyObject *timeout) const {
 #endif
 #endif
     if (timeout == Py_None) {
     if (timeout == Py_None) {
       _this->wait();
       _this->wait();
-    } else {
-      PyObject *num = PyNumber_Float(timeout);
-      if (num != nullptr) {
-        _this->wait(PyFloat_AS_DOUBLE(num));
-      } else {
-        return Dtool_Raise_ArgTypeError(timeout, 0, "result", "float");
-      }
+    }
+    else {
+      _this->wait(timeout_val);
     }
     }
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
     Py_BLOCK_THREADS
     Py_BLOCK_THREADS
 #endif
 #endif
 
 
+    if (python_task != nullptr) {
+      python_task->_fut_waiter = nullptr;
+    }
+
     if (!_this->done()) {
     if (!_this->done()) {
       // It timed out.  Raise an exception.
       // It timed out.  Raise an exception.
       static PyObject *exc_type = nullptr;
       static PyObject *exc_type = nullptr;

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

@@ -30,7 +30,7 @@ public:
   static PyObject *__iter__(PyObject *self) { return __await__(self); }
   static PyObject *__iter__(PyObject *self) { return __await__(self); }
 
 
   void set_result(PyObject *result);
   void set_result(PyObject *result);
-  PyObject *result(PyObject *timeout = Py_None) const;
+  PyObject *result(PyObject *self, PyObject *timeout = Py_None) const;
 
 
   PyObject *add_done_callback(PyObject *self, PyObject *fn);
   PyObject *add_done_callback(PyObject *self, PyObject *fn);
 
 

+ 72 - 23
panda/src/event/pythonTask.cxx

@@ -45,7 +45,7 @@ PythonTask(PyObject *func_or_coro, const std::string &name) :
   _exc_value(nullptr),
   _exc_value(nullptr),
   _exc_traceback(nullptr),
   _exc_traceback(nullptr),
   _generator(nullptr),
   _generator(nullptr),
-  _future_done(nullptr),
+  _fut_waiter(nullptr),
   _ignore_return(false),
   _ignore_return(false),
   _retrieved_exception(false) {
   _retrieved_exception(false) {
 
 
@@ -404,20 +404,58 @@ cancel() {
         << "Cancelling " << *this << "\n";
         << "Cancelling " << *this << "\n";
     }
     }
 
 
+    bool must_cancel = true;
+    if (_fut_waiter != nullptr) {
+      // Cancel the future that this task is waiting on.  Note that we do this
+      // before grabbing the lock, since this operation may also grab it.  This
+      // means that _fut_waiter is only protected by the GIL.
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+      // Use PyGILState to protect this asynchronous call.
+      PyGILState_STATE gstate;
+      gstate = PyGILState_Ensure();
+#endif
+
+      // Shortcut for unextended AsyncFuture.
+      if (Py_TYPE(_fut_waiter) == (PyTypeObject *)&Dtool_AsyncFuture) {
+        AsyncFuture *fut = (AsyncFuture *)DtoolInstance_VOID_PTR(_fut_waiter);
+        if (!fut->done()) {
+          fut->cancel();
+        }
+        if (fut->done()) {
+          // We don't need this anymore.
+          Py_DECREF(_fut_waiter);
+          _fut_waiter = nullptr;
+        }
+      }
+      else {
+        PyObject *result = PyObject_CallMethod(_fut_waiter, "cancel", nullptr);
+        Py_XDECREF(result);
+      }
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+      PyGILState_Release(gstate);
+#endif
+      // Keep _fut_waiter in any case, because we may need to cancel it again
+      // later if it ignores the cancellation.
+    }
+
     MutexHolder holder(manager->_lock);
     MutexHolder holder(manager->_lock);
     if (_state == S_awaiting) {
     if (_state == S_awaiting) {
       // Reactivate it so that it can receive a CancelledException.
       // Reactivate it so that it can receive a CancelledException.
-      _must_cancel = true;
+      if (must_cancel) {
+        _must_cancel = true;
+      }
       _state = AsyncTask::S_active;
       _state = AsyncTask::S_active;
       _chain->_active.push_back(this);
       _chain->_active.push_back(this);
       --_chain->_num_awaiting_tasks;
       --_chain->_num_awaiting_tasks;
       return true;
       return true;
     }
     }
-    else if (_future_done != nullptr) {
-      // We are polling, waiting for a non-Panda future to be done.
-      Py_DECREF(_future_done);
-      _future_done = nullptr;
-      _must_cancel = true;
+    else if (must_cancel || _fut_waiter != nullptr) {
+      // We may be polling an external future, so we still need to throw a
+      // CancelledException and allow it to be caught.
+      if (must_cancel) {
+        _must_cancel = true;
+      }
       return true;
       return true;
     }
     }
     else if (_chain->do_remove(this, true)) {
     else if (_chain->do_remove(this, true)) {
@@ -477,17 +515,24 @@ AsyncTask::DoneStatus PythonTask::
 do_python_task() {
 do_python_task() {
   PyObject *result = nullptr;
   PyObject *result = nullptr;
 
 
-  // Are we waiting for a future to finish?
-  if (_future_done != nullptr) {
-    PyObject *is_done = PyObject_CallNoArgs(_future_done);
-    if (!PyObject_IsTrue(is_done)) {
-      // Nope, ask again next frame.
+  // Are we waiting for a future to finish?  Short-circuit all the logic below
+  // by simply calling done().
+  {
+    PyObject *fut_waiter = _fut_waiter;
+    if (fut_waiter != nullptr) {
+      PyObject *is_done = PyObject_CallMethod(fut_waiter, "done", nullptr);
+      if (is_done == nullptr) {
+        return DS_interrupt;
+      }
+      if (!PyObject_IsTrue(is_done)) {
+        // Nope, ask again next frame.
+        Py_DECREF(is_done);
+        return DS_cont;
+      }
       Py_DECREF(is_done);
       Py_DECREF(is_done);
-      return DS_cont;
+      Py_DECREF(fut_waiter);
+      _fut_waiter = nullptr;
     }
     }
-    Py_DECREF(is_done);
-    Py_DECREF(_future_done);
-    _future_done = nullptr;
   }
   }
 
 
   if (_generator == nullptr) {
   if (_generator == nullptr) {
@@ -664,7 +709,9 @@ do_python_task() {
           task_cat.error()
           task_cat.error()
             << *this << " cannot await itself\n";
             << *this << " cannot await itself\n";
         }
         }
-        Py_DECREF(result);
+        // Store the Python object in case we need to cancel it (it may be a
+        // subclass of AsyncFuture that overrides cancel() from Python)
+        _fut_waiter = result;
         return DS_await;
         return DS_await;
       }
       }
     } else {
     } else {
@@ -674,8 +721,9 @@ do_python_task() {
       if (check != nullptr && check != Py_None) {
       if (check != nullptr && check != Py_None) {
         Py_DECREF(check);
         Py_DECREF(check);
         // Next frame, check whether this future is done.
         // Next frame, check whether this future is done.
-        _future_done = PyObject_GetAttrString(result, "done");
-        if (_future_done == nullptr || !PyCallable_Check(_future_done)) {
+        PyObject *fut_done = PyObject_GetAttrString(result, "done");
+        if (fut_done == nullptr || !PyCallable_Check(fut_done)) {
+          Py_XDECREF(fut_done);
           task_cat.error()
           task_cat.error()
             << "future.done is not callable\n";
             << "future.done is not callable\n";
           return DS_interrupt;
           return DS_interrupt;
@@ -686,7 +734,7 @@ do_python_task() {
             << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
             << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
           Py_DECREF(str);
           Py_DECREF(str);
         }
         }
-        Py_DECREF(result);
+        _fut_waiter = result;
         return DS_cont;
         return DS_cont;
       }
       }
       PyErr_Clear();
       PyErr_Clear();
@@ -802,9 +850,10 @@ upon_death(AsyncTaskManager *manager, bool clean_exit) {
   AsyncTask::upon_death(manager, clean_exit);
   AsyncTask::upon_death(manager, clean_exit);
 
 
   // If we were polling something when we were removed, get rid of it.
   // If we were polling something when we were removed, get rid of it.
-  if (_future_done != nullptr) {
-    Py_DECREF(_future_done);
-    _future_done = nullptr;
+  //TODO: should we call cancel() on it?
+  if (_fut_waiter != nullptr) {
+    Py_DECREF(_fut_waiter);
+    _fut_waiter = nullptr;
   }
   }
 
 
   if (_upon_death != Py_None) {
   if (_upon_death != Py_None) {

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

@@ -115,7 +115,7 @@ private:
   PyObject *_exc_traceback;
   PyObject *_exc_traceback;
 
 
   PyObject *_generator;
   PyObject *_generator;
-  PyObject *_future_done;
+  PyObject *_fut_waiter;
 
 
   bool _append_task;
   bool _append_task;
   bool _ignore_return;
   bool _ignore_return;

+ 4 - 0
panda/src/express/virtualFileSystem.cxx

@@ -649,6 +649,10 @@ find_file(const Filename &filename, const DSearchPath &searchpath,
     return get_file(filename, status_only);
     return get_file(filename, status_only);
   }
   }
 
 
+  if (filename.empty()) {
+    return nullptr;
+  }
+
   int num_directories = searchpath.get_num_directories();
   int num_directories = searchpath.get_num_directories();
   for (int i = 0; i < num_directories; ++i) {
   for (int i = 0; i < num_directories; ++i) {
     Filename match(searchpath.get_directory(i), filename);
     Filename match(searchpath.get_directory(i), filename);

+ 30 - 5
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -952,18 +952,43 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot,
             }
             }
           } else if (_fb_properties.get_float_color()) {
           } else if (_fb_properties.get_float_color()) {
             // 16-bit floating-point.
             // 16-bit floating-point.
-            if (_fb_properties.get_blue_bits() > 0 ||
+            if (_fb_properties.get_blue_bits() > 10 ||
                 _fb_properties.get_color_bits() == 1 ||
                 _fb_properties.get_color_bits() == 1 ||
-                _fb_properties.get_color_bits() > 16 * 2) {
+                _fb_properties.get_color_bits() > 32) {
               gl_format = GL_RGB16F;
               gl_format = GL_RGB16F;
+            } else if (_fb_properties.get_blue_bits() > 0) {
+              if (_fb_properties.get_red_bits() > 11 ||
+                  _fb_properties.get_green_bits() > 11) {
+                gl_format = GL_RGB16F;
+              } else {
+                gl_format = GL_R11F_G11F_B10F;
+              }
             } else if (_fb_properties.get_green_bits() > 0 ||
             } else if (_fb_properties.get_green_bits() > 0 ||
                        _fb_properties.get_color_bits() > 16) {
                        _fb_properties.get_color_bits() > 16) {
               gl_format = GL_RG16F;
               gl_format = GL_RG16F;
             } else {
             } else {
               gl_format = GL_R16F;
               gl_format = GL_R16F;
             }
             }
-          } else if (_fb_properties.get_color_bits() > 8 * 3) {
-            gl_format = GL_RGB16_EXT;
+          } else if (_fb_properties.get_color_bits() > 10 * 3 ||
+                     _fb_properties.get_red_bits() > 10 ||
+                     _fb_properties.get_green_bits() > 10 ||
+                     _fb_properties.get_blue_bits() > 10) {
+            // 16-bit normalized.
+            if (_fb_properties.get_blue_bits() > 0 ||
+                _fb_properties.get_color_bits() == 1 ||
+                _fb_properties.get_color_bits() > 16 * 2) {
+              gl_format = GL_RGBA16;
+            } else if (_fb_properties.get_green_bits() > 0 ||
+                       _fb_properties.get_color_bits() > 16) {
+              gl_format = GL_RG16;
+            } else {
+              gl_format = GL_R16;
+            }
+          } else if (_fb_properties.get_color_bits() > 8 * 3 ||
+                     _fb_properties.get_red_bits() > 8 ||
+                     _fb_properties.get_green_bits() > 8 ||
+                     _fb_properties.get_blue_bits() > 8) {
+            gl_format = GL_RGB10_A2;
           } else {
           } else {
             gl_format = GL_RGB;
             gl_format = GL_RGB;
           }
           }
@@ -980,7 +1005,7 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot,
             if (_fb_properties.get_color_bits() > 16 * 3) {
             if (_fb_properties.get_color_bits() > 16 * 3) {
               gl_format = GL_RGBA32F_ARB;
               gl_format = GL_RGBA32F_ARB;
             } else if (_fb_properties.get_color_bits() > 8 * 3) {
             } else if (_fb_properties.get_color_bits() > 8 * 3) {
-              gl_format = GL_RGBA16_EXT;
+              gl_format = GL_RGBA16;
             } else {
             } else {
               gl_format = GL_RGBA;
               gl_format = GL_RGBA;
             }
             }

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

@@ -7421,7 +7421,10 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
   switch (format) {
   switch (format) {
   case Texture::F_depth_stencil:
   case Texture::F_depth_stencil:
     if (_current_properties->get_float_depth()) {
     if (_current_properties->get_float_depth()) {
+      //NB. In the future we may need a T_float_32_unsigned_int_24_8 format, but
+      // for now we'll just try to grab the depth component.
       component_type = Texture::T_float;
       component_type = Texture::T_float;
+      format = Texture::F_depth_component32;
     } else {
     } else {
       component_type = Texture::T_unsigned_int_24_8;
       component_type = Texture::T_unsigned_int_24_8;
     }
     }
@@ -7457,21 +7460,66 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
       }
       }
     } else if (_current_properties->get_float_color()) {
     } else if (_current_properties->get_float_color()) {
       if (_current_properties->get_alpha_bits()) {
       if (_current_properties->get_alpha_bits()) {
-        format = Texture::F_rgba32;
+        if (_current_properties->get_red_bits() == 16 &&
+            _current_properties->get_green_bits() == 16 &&
+            _current_properties->get_blue_bits() == 16 &&
+            _current_properties->get_alpha_bits() == 16) {
+          format = Texture::F_rgba16;
+        } else {
+          format = Texture::F_rgba32;
+        }
       } else if (_current_properties->get_blue_bits()) {
       } else if (_current_properties->get_blue_bits()) {
-        format = Texture::F_rgb32;
+        if (_current_properties->get_red_bits() == 11 &&
+            _current_properties->get_green_bits() == 11 &&
+            _current_properties->get_blue_bits() == 10) {
+          format = Texture::F_r11_g11_b10;
+        } else if (_current_properties->get_red_bits() == 16 &&
+                   _current_properties->get_green_bits() == 16 &&
+                   _current_properties->get_blue_bits() == 16) {
+          format = Texture::F_rgb16;
+        } else {
+          format = Texture::F_rgb32;
+        }
       } else if (_current_properties->get_green_bits()) {
       } else if (_current_properties->get_green_bits()) {
-        format = Texture::F_rg32;
+        if (_current_properties->get_red_bits() == 16 &&
+            _current_properties->get_green_bits() == 16) {
+          format = Texture::F_rg16;
+        } else {
+          format = Texture::F_rg32;
+        }
       } else {
       } else {
-        format = Texture::F_r32;
+        if (_current_properties->get_red_bits() == 16) {
+          format = Texture::F_r16;
+        } else {
+          format = Texture::F_r32;
+        }
       }
       }
-    } else {
-      if (_current_properties->get_alpha_bits()) {
+    } else if (_current_properties->get_alpha_bits()) {
+      if (_current_properties->get_red_bits() == 10 &&
+          _current_properties->get_green_bits() == 10 &&
+          _current_properties->get_blue_bits() == 10 &&
+          _current_properties->get_alpha_bits() == 2) {
+        format = Texture::F_rgb10_a2;
+      } else {
         format = Texture::F_rgba;
         format = Texture::F_rgba;
+      }
+    } else if (_current_properties->get_blue_bits()) {
+      format = Texture::F_rgb;
+    } else if (_current_properties->get_green_bits()) {
+      if (_current_properties->get_red_bits() == 16 &&
+          _current_properties->get_green_bits() == 16) {
+        format = Texture::F_rg16;
+      } else {
+        format = Texture::F_rg;
+      }
+    } else {
+      if (_current_properties->get_color_bits() == 16) {
+        format = Texture::F_r16;
       } else {
       } else {
-        format = Texture::F_rgb;
+        format = Texture::F_red;
       }
       }
     }
     }
+
     if (_current_properties->get_float_color()) {
     if (_current_properties->get_float_color()) {
       component_type = Texture::T_float;
       component_type = Texture::T_float;
     } else if (_current_properties->get_color_bits() <= 24
     } else if (_current_properties->get_color_bits() <= 24
@@ -7524,6 +7572,7 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     external_format = GL_RGBA;
     external_format = GL_RGBA;
   }
   }
 
 
+#ifndef NDEBUG
   if (GLCAT.is_spam()) {
   if (GLCAT.is_spam()) {
     GLCAT.spam()
     GLCAT.spam()
       << "glReadPixels(" << xo << ", " << yo << ", " << w << ", " << h << ", ";
       << "glReadPixels(" << xo << ", " << yo << ", " << w << ", " << h << ", ";
@@ -7567,10 +7616,72 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     case GL_FLOAT:
     case GL_FLOAT:
       GLCAT.spam(false) << "GL_FLOAT";
       GLCAT.spam(false) << "GL_FLOAT";
       break;
       break;
+    case GL_UNSIGNED_SHORT_4_4_4_4:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_4_4_4_4";
+      break;
+    case GL_UNSIGNED_SHORT_5_5_5_1:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_5_5_5_1";
+      break;
+    case GL_UNSIGNED_SHORT_5_6_5:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_5_6_5";
+      break;
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
     case GL_INT:
     case GL_INT:
       GLCAT.spam(false) << "GL_INT";
       GLCAT.spam(false) << "GL_INT";
       break;
       break;
+    case GL_BYTE:
+      GLCAT.spam(false) << "GL_BYTE";
+      break;
+    case GL_SHORT:
+      GLCAT.spam(false) << "GL_SHORT";
+      break;
+    case GL_UNSIGNED_INT:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT";
+      break;
+    case GL_HALF_FLOAT:
+      GLCAT.spam(false) << "GL_HALF_FLOAT";
+      break;
+#endif
+#ifndef OPENGLES
+    case GL_UNSIGNED_BYTE_3_3_2:
+      GLCAT.spam(false) << "GL_UNSIGNED_BYTE_3_3_2";
+      break;
+    case GL_UNSIGNED_BYTE_2_3_3_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_BYTE_2_3_3_REV";
+      break;
+    case GL_UNSIGNED_SHORT_5_6_5_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_5_6_5_REV";
+      break;
+    case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_4_4_4_4_REV";
+      break;
+    case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_SHORT_1_5_5_5_REV";
+      break;
+    case GL_UNSIGNED_INT_8_8_8_8:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_8_8_8_8";
+      break;
+    case GL_UNSIGNED_INT_8_8_8_8_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_8_8_8_8_REV";
+      break;
+    case GL_UNSIGNED_INT_10_10_10_2:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_10_10_10_2";
+      break;
+    case GL_UNSIGNED_INT_2_10_10_10_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_2_10_10_10_REV";
+      break;
+    case GL_UNSIGNED_INT_24_8:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_24_8";
+      break;
+    case GL_UNSIGNED_INT_10F_11F_11F_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_10F_11F_11F_REV";
+      break;
+    case GL_UNSIGNED_INT_5_9_9_9_REV:
+      GLCAT.spam(false) << "GL_UNSIGNED_INT_5_9_9_9_REV";
+      break;
+    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+      GLCAT.spam(false) << "GL_FLOAT_32_UNSIGNED_INT_24_8_REV";
+      break;
 #endif
 #endif
     default:
     default:
       GLCAT.spam(false) << "unknown";
       GLCAT.spam(false) << "unknown";
@@ -7579,6 +7690,7 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     GLCAT.spam(false)
     GLCAT.spam(false)
       << ")" << endl;
       << ")" << endl;
   }
   }
+#endif  // NDEBUG
 
 
   unsigned char *image_ptr = tex->modify_ram_image();
   unsigned char *image_ptr = tex->modify_ram_image();
   size_t image_size = tex->get_ram_image_size();
   size_t image_size = tex->get_ram_image_size();

+ 9 - 2
panda/src/pgraphnodes/nodeCullCallbackData.cxx

@@ -56,6 +56,13 @@ upcall() {
     }
     }
   }
   }
 
 
-  // Now traverse below.
-  _trav->traverse_below(_data);
+  // Now visit all the node's children.
+  PandaNodePipelineReader *node_reader = _data.node_reader();
+  PandaNode::Children children = node_reader->get_children();
+  node_reader->release();
+  int num_children = children.get_num_children();
+  for (int i = 0; i < num_children; ++i) {
+    CullTraverserData next_data(_data, children.get_child(i));
+    _trav->traverse(next_data);
+  }
 }
 }

+ 1 - 6
panda/src/pgui/pgItem.cxx

@@ -1018,12 +1018,7 @@ set_frame_style(int state, const PGFrameStyle &style) {
   _state_defs[state]._frame_stale = true;
   _state_defs[state]._frame_stale = true;
 
 
   mark_internal_bounds_stale();
   mark_internal_bounds_stale();
-
-#ifdef THREADED_PIPELINE
-  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
-    update_frame(state);
-  }
-#endif
+  update_frame(state);
 }
 }
 
 
 #ifdef HAVE_AUDIO
 #ifdef HAVE_AUDIO

+ 4 - 2
panda/src/pnmtext/freetypeFont.cxx

@@ -108,12 +108,14 @@ load_font(const Filename &font_filename, int face_index) {
   vfs->resolve_filename(path, get_model_path());
   vfs->resolve_filename(path, get_model_path());
   exists = vfs->read_file(path, _face->_font_data, true);
   exists = vfs->read_file(path, _face->_font_data, true);
   if (exists) {
   if (exists) {
-    FT_Face face;
+    FT_Face face = 0;
     error = FT_New_Memory_Face(_face->_ft_library,
     error = FT_New_Memory_Face(_face->_ft_library,
                                (const FT_Byte *)_face->_font_data.data(),
                                (const FT_Byte *)_face->_font_data.data(),
                                _face->_font_data.length(),
                                _face->_font_data.length(),
                                face_index, &face);
                                face_index, &face);
-    _face->set_face(face);
+    if (face) {
+      _face->set_face(face);
+    }
   }
   }
 
 
   bool okflag = false;
   bool okflag = false;

+ 15 - 35
pandatool/src/maya/eggObjectFlags.mel

@@ -1,6 +1,6 @@
 //
 //
-// This is a sample mel script to flag a Maya node with one, two, or
-// three eggObjectTypes* attributes.  These attributes are used to tag
+// This is a sample mel script to flag a Maya node with at least
+// one eggObjectTypes* attribute.  These attributes are used to tag
 // geometry with special meaning to Panda.  There are a handful of
 // geometry with special meaning to Panda.  There are a handful of
 // attribute values that are predefined within Panda, but you can also
 // attribute values that are predefined within Panda, but you can also
 // make up your own attribute values, and define an arbitrary egg
 // make up your own attribute values, and define an arbitrary egg
@@ -28,42 +28,22 @@
 // direct/src/configfiles/direct.prc.pp for examples of this.
 // direct/src/configfiles/direct.prc.pp for examples of this.
 //
 //
 
 
-global proc eggObjectFlags()
-{       
-string $sel[] =`ls -sl`;
-for ($i in $sel)
-  {
-  string $attrName = "eggObjectTypes";
+global proc eggObjectFlags() {
+  string $sel[] =`ls -sl`;
 
 
   // Modify this line as needed to add your own object types.
   // Modify this line as needed to add your own object types.
   string $eggFlags = "none:portal:polylight:seq24:seq12:indexed:model:dcs:barrier:sphere:tube:trigger:trigger-sphere:bubble:ghost:keep-all-uvsets";
   string $eggFlags = "none:portal:polylight:seq24:seq12:indexed:model:dcs:barrier:sphere:tube:trigger:trigger-sphere:bubble:ghost:keep-all-uvsets";
-  
-  string $object = ($i + ".eggObjectTypes");
-  
-    if( `objExists ($object + "1")` ) 
-    {
-    
-      if( `objExists ($object + "2")` ) 
-      {
-      
-        if( `objExists ($object + "3")` ) 
-        {
-        warning("No More Egg Object Types Supported");
-        } 
-         else 
-        {
-        addAttr -ln ($attrName + "3") -k 1 -at "enum" -en ($eggFlags)  $i;
-        }
-      
-      } 
-       else 
-      {
-      addAttr -ln ($attrName + "2") -k 1 -at "enum" -en ($eggFlags)  $i;
-      }
-    } 
-     else 
-    {
-    addAttr -ln ($attrName + "1") -k 1 -at "enum" -en ($eggFlags)  $i;
+
+  for ($i in $sel) {
+    string $attrName = "eggObjectTypes";
+    string $object = ($i + "." + $attrName);
+
+    int $num = 1;
+
+    while (`objExists ($object + $num)`) {
+      $num++;
     }
     }
+
+    addAttr -ln ($attrName + $num) -k 1 -at "enum" -en ($eggFlags) $i;
   }
   }
 }
 }

+ 17 - 7
pandatool/src/mayaegg/mayaNodeTree.cxx

@@ -32,6 +32,8 @@
 #include <maya/MGlobal.h>
 #include <maya/MGlobal.h>
 #include "post_maya_include.h"
 #include "post_maya_include.h"
 
 
+#include <sstream>
+
 using std::string;
 using std::string;
 
 
 /**
 /**
@@ -341,13 +343,21 @@ get_egg_group(MayaNodeDesc *node_desc) {
       MObject dag_object = node_desc->get_dag_path().node();
       MObject dag_object = node_desc->get_dag_path().node();
       string object_type;
       string object_type;
       LVector3d value;
       LVector3d value;
-      if (get_enum_attribute(dag_object, "eggObjectTypes1", object_type)) {
-        egg_group->add_object_type(object_type);
-      }
-      if (get_enum_attribute(dag_object, "eggObjectTypes2", object_type)) {
-        egg_group->add_object_type(object_type);
-      }
-      if (get_enum_attribute(dag_object, "eggObjectTypes3", object_type)) {
+
+      for (unsigned int i = 1; ; i++) {
+        std::ostringstream attr;
+        attr << "eggObjectTypes" << i;
+
+        if (!get_enum_attribute(dag_object, attr.str(), object_type)) {
+          if (i < 3) {
+            // Support out-of-order legacy object types.
+            continue;
+          }
+
+          // We have run out of object types to add.
+          break;
+        }
+
         egg_group->add_object_type(object_type);
         egg_group->add_object_type(object_type);
       }
       }
 
 

+ 281 - 0
tests/event/test_futures.py

@@ -9,6 +9,33 @@ else:
     from concurrent.futures._base import TimeoutError, CancelledError
     from concurrent.futures._base import TimeoutError, CancelledError
 
 
 
 
+class MockFuture:
+    _asyncio_future_blocking = False
+    _state = 'PENDING'
+    _cancel_return = False
+    _result = None
+
+    def __await__(self):
+        while self._state == 'PENDING':
+            yield self
+        return self.result()
+
+    def done(self):
+        return self._state != 'PENDING'
+
+    def cancelled(self):
+        return self._state == 'CANCELLED'
+
+    def cancel(self):
+        return self._cancel_return
+
+    def result(self):
+        if self._state == 'CANCELLED':
+            raise CancelledError
+
+        return self._result
+
+
 def test_future_cancelled():
 def test_future_cancelled():
     fut = core.AsyncFuture()
     fut = core.AsyncFuture()
 
 
@@ -123,6 +150,66 @@ def test_task_cancel_during_run():
         task.result()
         task.result()
 
 
 
 
+def test_task_cancel_waiting():
+    # Calling result() in a threaded task chain should cancel the future being
+    # waited on if the surrounding task is cancelled.
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_chain = task_mgr.make_task_chain("test_task_cancel_waiting")
+    task_chain.set_num_threads(1)
+
+    fut = core.AsyncFuture()
+
+    async def task_main(task):
+        # This will block the thread this task is in until the future is done,
+        # or until the task is cancelled (which implicitly cancels the future).
+        fut.result()
+        return task.done
+
+    task = core.PythonTask(task_main, 'task_main')
+    task.set_task_chain(task_chain.name)
+    task_mgr.add(task)
+
+    task_chain.start_threads()
+    try:
+        assert not task.done()
+        fut.cancel()
+        task.wait()
+
+        assert task.cancelled()
+        assert fut.cancelled()
+
+    finally:
+        task_chain.stop_threads()
+
+
+def test_task_cancel_awaiting():
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_chain = task_mgr.make_task_chain("test_task_cancel_awaiting")
+
+    fut = core.AsyncFuture()
+
+    async def task_main(task):
+        await fut
+        return task.done
+
+    task = core.PythonTask(task_main, 'task_main')
+    task.set_task_chain(task_chain.name)
+    task_mgr.add(task)
+
+    task_chain.poll()
+    assert not task.done()
+
+    task_chain.poll()
+    assert not task.done()
+
+    task.cancel()
+    task_chain.poll()
+    assert task.done()
+    assert task.cancelled()
+    assert fut.done()
+    assert fut.cancelled()
+
+
 def test_task_result():
 def test_task_result():
     task_mgr = core.AsyncTaskManager.get_global_ptr()
     task_mgr = core.AsyncTaskManager.get_global_ptr()
     task_chain = task_mgr.make_task_chain("test_task_result")
     task_chain = task_mgr.make_task_chain("test_task_result")
@@ -144,6 +231,140 @@ def test_task_result():
     assert task.result() == 42
     assert task.result() == 42
 
 
 
 
+def test_coro_await_coro():
+    # Await another coro in a coro.
+    fut = core.AsyncFuture()
+    async def coro2():
+        await fut
+
+    async def coro_main():
+        await coro2()
+
+    task = core.PythonTask(coro_main())
+
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_mgr.add(task)
+    for i in range(5):
+        task_mgr.poll()
+
+    assert not task.done()
+    fut.set_result(None)
+    task_mgr.poll()
+    assert task.done()
+    assert not task.cancelled()
+
+
+def test_coro_await_cancel_resistant_coro():
+    # Await another coro in a coro, but cancel the outer.
+    fut = core.AsyncFuture()
+    cancelled_caught = [0]
+    keep_going = [False]
+
+    async def cancel_resistant_coro():
+        while not fut.done():
+            try:
+                await core.AsyncFuture.shield(fut)
+            except CancelledError as ex:
+                cancelled_caught[0] += 1
+
+    async def coro_main():
+        await cancel_resistant_coro()
+
+    task = core.PythonTask(coro_main(), 'coro_main')
+
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_mgr.add(task)
+    assert not task.done()
+
+    task_mgr.poll()
+    assert not task.done()
+
+    # No cancelling it once it started...
+    for i in range(3):
+        assert task.cancel()
+        assert not task.done()
+
+        for j in range(3):
+            task_mgr.poll()
+            assert not task.done()
+
+    assert cancelled_caught[0] == 3
+
+    fut.set_result(None)
+    task_mgr.poll()
+    assert task.done()
+    assert not task.cancelled()
+
+
+def test_coro_await_external():
+    # Await an external future in a coro.
+    fut = MockFuture()
+    fut._result = 12345
+    res = []
+
+    async def coro_main():
+        res.append(await fut)
+
+    task = core.PythonTask(coro_main(), 'coro_main')
+
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_mgr.add(task)
+    for i in range(5):
+        task_mgr.poll()
+
+    assert not task.done()
+    fut._state = 'FINISHED'
+    task_mgr.poll()
+    assert task.done()
+    assert not task.cancelled()
+    assert res == [12345]
+
+
+def test_coro_await_external_cancel_inner():
+    # Cancel external future being awaited by a coro.
+    fut = MockFuture()
+
+    async def coro_main():
+        await fut
+
+    task = core.PythonTask(coro_main(), 'coro_main')
+
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_mgr.add(task)
+    for i in range(5):
+        task_mgr.poll()
+
+    assert not task.done()
+    fut._state = 'CANCELLED'
+    assert not task.done()
+    task_mgr.poll()
+    assert task.done()
+    assert task.cancelled()
+
+
+def test_coro_await_external_cancel_outer():
+    # Cancel task that is awaiting external future.
+    fut = MockFuture()
+    result = []
+
+    async def coro_main():
+        result.append(await fut)
+
+    task = core.PythonTask(coro_main(), 'coro_main')
+
+    task_mgr = core.AsyncTaskManager.get_global_ptr()
+    task_mgr.add(task)
+    for i in range(5):
+        task_mgr.poll()
+
+    assert not task.done()
+    fut._state = 'CANCELLED'
+    assert not task.done()
+    task_mgr.poll()
+    assert task.done()
+    assert task.cancelled()
+
+
 def test_coro_exception():
 def test_coro_exception():
     task_mgr = core.AsyncTaskManager.get_global_ptr()
     task_mgr = core.AsyncTaskManager.get_global_ptr()
     task_chain = task_mgr.make_task_chain("test_coro_exception")
     task_chain = task_mgr.make_task_chain("test_coro_exception")
@@ -289,6 +510,66 @@ def test_future_gather_cancel_outer():
         assert gather.result()
         assert gather.result()
 
 
 
 
+def test_future_shield():
+    # An already done future is returned as-is (no cancellation can occur)
+    inner = core.AsyncFuture()
+    inner.set_result(None)
+    outer = core.AsyncFuture.shield(inner)
+    assert inner == outer
+
+    # Normally finishing future
+    inner = core.AsyncFuture()
+    outer = core.AsyncFuture.shield(inner)
+    assert not outer.done()
+    inner.set_result(None)
+    assert outer.done()
+    assert not outer.cancelled()
+    assert inner.result() is None
+
+    # Normally finishing future with result
+    inner = core.AsyncFuture()
+    outer = core.AsyncFuture.shield(inner)
+    assert not outer.done()
+    inner.set_result(123)
+    assert outer.done()
+    assert not outer.cancelled()
+    assert inner.result() == 123
+
+    # Cancelled inner future does propagate cancellation outward
+    inner = core.AsyncFuture()
+    outer = core.AsyncFuture.shield(inner)
+    assert not outer.done()
+    inner.cancel()
+    assert outer.done()
+    assert outer.cancelled()
+
+    # Finished outer future does nothing to inner
+    inner = core.AsyncFuture()
+    outer = core.AsyncFuture.shield(inner)
+    outer.set_result(None)
+    assert not inner.done()
+    inner.cancel()
+    assert not outer.cancelled()
+
+    # Cancelled outer future does nothing to inner
+    inner = core.AsyncFuture()
+    outer = core.AsyncFuture.shield(inner)
+    outer.cancel()
+    assert not inner.done()
+    inner.cancel()
+
+    # Can be shielded multiple times
+    inner = core.AsyncFuture()
+    outer1 = core.AsyncFuture.shield(inner)
+    outer2 = core.AsyncFuture.shield(inner)
+    outer1.cancel()
+    assert not inner.done()
+    assert not outer2.done()
+    inner.cancel()
+    assert outer1.done()
+    assert outer2.done()
+
+
 def test_future_done_callback():
 def test_future_done_callback():
     fut = core.AsyncFuture()
     fut = core.AsyncFuture()