Browse Source

Merge branch 'master' into cmake

Sam Edwards 9 years ago
parent
commit
dad14499cd
83 changed files with 2863 additions and 324 deletions
  1. 4 0
      direct/src/directscripts/eggcacher.py
  2. 3 0
      direct/src/directscripts/packpanda.py
  3. 5 0
      doc/ReleaseNotes
  4. 9 2
      dtool/src/dtoolbase/deletedBufferChain.cxx
  5. 1 5
      dtool/src/dtoolbase/deletedBufferChain.h
  6. 1 1
      dtool/src/dtoolbase/dtoolbase.h
  7. 16 4
      dtool/src/dtoolbase/memoryHook.I
  8. 5 0
      dtool/src/dtoolbase/memoryHook.cxx
  9. 2 0
      dtool/src/dtoolbase/neverFreeMemory.I
  10. 3 7
      dtool/src/dtoolbase/neverFreeMemory.cxx
  11. 42 33
      dtool/src/prc/encryptStreamBuf.cxx
  12. 2 4
      dtool/src/prc/encryptStreamBuf.h
  13. 3 0
      dtool/src/prc/notify.cxx
  14. 4 0
      dtool/src/pystub/pystub.cxx
  15. 4 0
      makepanda/installer.nsi
  16. 44 25
      makepanda/makepanda.py
  17. 598 0
      makepanda/makewheel.py
  18. 14 7
      panda/src/cocoadisplay/cocoaGraphicsPipe.mm
  19. 0 13
      panda/src/display/get_x11.h
  20. 8 0
      panda/src/display/graphicsStateGuardian.I
  21. 22 2
      panda/src/display/graphicsStateGuardian.cxx
  22. 8 0
      panda/src/display/graphicsStateGuardian.h
  23. 17 7
      panda/src/downloader/httpClient.cxx
  24. 8 0
      panda/src/ffmpeg/config_ffmpeg.cxx
  25. 1 0
      panda/src/ffmpeg/config_ffmpeg.h
  26. 48 18
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  27. 2 0
      panda/src/ffmpeg/ffmpegVideoCursor.h
  28. 25 0
      panda/src/glstuff/glBufferContext_src.I
  29. 49 0
      panda/src/glstuff/glBufferContext_src.cxx
  30. 52 0
      panda/src/glstuff/glBufferContext_src.h
  31. 160 9
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  32. 14 0
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  33. 53 0
      panda/src/glstuff/glShaderContext_src.cxx
  34. 12 0
      panda/src/glstuff/glShaderContext_src.h
  35. 1 0
      panda/src/glstuff/glmisc_src.cxx
  36. 1 0
      panda/src/glstuff/glstuff_src.cxx
  37. 1 0
      panda/src/glstuff/glstuff_src.h
  38. 1 0
      panda/src/glxdisplay/panda_glxext.h
  39. 1 0
      panda/src/gobj/p3gobj_composite2.cxx
  40. 5 2
      panda/src/gobj/preparedGraphicsObjects.I
  41. 165 0
      panda/src/gobj/preparedGraphicsObjects.cxx
  42. 19 0
      panda/src/gobj/preparedGraphicsObjects.h
  43. 36 3
      panda/src/gobj/shader.cxx
  44. 63 0
      panda/src/gobj/shaderBuffer.I
  45. 193 0
      panda/src/gobj/shaderBuffer.cxx
  46. 97 0
      panda/src/gobj/shaderBuffer.h
  47. 1 0
      panda/src/gobj/shaderContext.h
  48. 127 24
      panda/src/gobj/texture.cxx
  49. 2 0
      panda/src/gobj/texture.h
  50. 1 1
      panda/src/grutil/config_grutil.cxx
  51. 5 0
      panda/src/gsgbase/graphicsStateGuardianBase.h
  52. 6 2
      panda/src/movies/dr_flac.h
  53. 1 0
      panda/src/pgraph/light.h
  54. 8 0
      panda/src/pgraph/nodePath.I
  55. 2 0
      panda/src/pgraph/nodePath.h
  56. 32 2
      panda/src/pgraph/shaderAttrib.cxx
  57. 1 0
      panda/src/pgraph/shaderAttrib.h
  58. 12 0
      panda/src/pgraph/shaderInput.I
  59. 6 1
      panda/src/pgraph/shaderInput.h
  60. 3 0
      panda/src/pgraphnodes/config_pgraphnodes.cxx
  61. 1 0
      panda/src/pgraphnodes/p3pgraphnodes_composite2.cxx
  62. 59 0
      panda/src/pgraphnodes/rectangleLight.I
  63. 150 0
      panda/src/pgraphnodes/rectangleLight.cxx
  64. 98 0
      panda/src/pgraphnodes/rectangleLight.h
  65. 18 0
      panda/src/pipeline/pythonThread.cxx
  66. 2 2
      panda/src/pnmimagetypes/pnmFileTypeTIFF.cxx
  67. 3 3
      panda/src/pstatclient/pStatProperties.cxx
  68. 16 3
      panda/src/putil/cachedTypedWritableReferenceCount.I
  69. 4 1
      panda/src/putil/cachedTypedWritableReferenceCount.h
  70. 17 0
      panda/src/putil/copyOnWriteObject.cxx
  71. 3 0
      panda/src/putil/copyOnWriteObject.h
  72. 86 11
      panda/src/putil/copyOnWritePointer.I
  73. 12 16
      panda/src/putil/copyOnWritePointer.cxx
  74. 9 5
      panda/src/putil/copyOnWritePointer.h
  75. 66 0
      panda/src/vision/webcamVideoV4L.cxx
  76. 8 7
      panda/src/windisplay/winGraphicsWindow.cxx
  77. 5 0
      panda/src/x11display/config_x11display.cxx
  78. 1 0
      panda/src/x11display/config_x11display.h
  79. 32 0
      panda/src/x11display/x11GraphicsPipe.I
  80. 113 25
      panda/src/x11display/x11GraphicsPipe.cxx
  81. 56 0
      panda/src/x11display/x11GraphicsPipe.h
  82. 72 68
      panda/src/x11display/x11GraphicsWindow.cxx
  83. 3 11
      panda/src/x11display/x11GraphicsWindow.h

+ 4 - 0
direct/src/directscripts/eggcacher.py

@@ -88,3 +88,7 @@ class EggCacher:
             progress += size
 
 cacher = EggCacher(sys.argv[1:])
+
+# Dummy main function so this can be added to console_scripts.
+def main():
+    return 0

+ 3 - 0
direct/src/directscripts/packpanda.py

@@ -419,3 +419,6 @@ else:
     if not(os.path.exists("/usr/bin/rpmbuild") or os.path.exists("/usr/bin/dpkg-deb")):
         exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!")
 
+# Dummy main function so this can be added to console_scripts.
+def main():
+    return 0

+ 5 - 0
doc/ReleaseNotes

@@ -2,6 +2,7 @@
 
 This issue fixes several bugs that were still found in 1.9.2.
 
+* Fix crash when using homebrew Python on Mac OS X
 * Fix crash when running in Steam on Linux when using OpenAL
 * Fix crash using wx/tkinter on Mac as long as want-wx/tk is set
 * Fix loading models from 'models' package with models/ prefix
@@ -50,6 +51,10 @@ This issue fixes several bugs that were still found in 1.9.2.
 * GLSL: fix error when legacy matrix generator inputs are mat3
 * Now tries to preserve refresh rate when switching fullscreen on Windows
 * Fix back-to-front sorting when gl-coordinate-system is changed
+* Now also compiles on older Linux distros (eg. CentOS 5 / manylinux1)
+* get_keyboard_map now includes keys on layouts with special characters
+* Fix crash due to incorrect alignment when compiling Eigen with AVX
+* Fix crash when writing 16-bit .tif file (now silently downsamples)
 
 ------------------------  RELEASE 1.9.2  ------------------------
 

+ 9 - 2
dtool/src/dtoolbase/deletedBufferChain.cxx

@@ -39,7 +39,7 @@ allocate(size_t size, TypeHandle type_handle) {
   assert(size <= _buffer_size);
 
   // Determine how much space to allocate.
-  const size_t alloc_size = _buffer_size + flag_reserved_bytes;
+  const size_t alloc_size = _buffer_size + flag_reserved_bytes + MemoryHook::get_memory_alignment() - 1;
 
   ObjectNode *obj;
 
@@ -69,7 +69,10 @@ allocate(size_t size, TypeHandle type_handle) {
   // If we get here, the deleted_chain is empty; we have to allocate a new
   // object from the system pool.
 
-  obj = (ObjectNode *)NeverFreeMemory::alloc(alloc_size);
+  // Allocate memory, and make sure the object starts at the proper alignment.
+  void *mem = NeverFreeMemory::alloc(alloc_size);
+  intptr_t pad = ((intptr_t)flag_reserved_bytes - (intptr_t)mem) % MemoryHook::get_memory_alignment();
+  obj = (ObjectNode *)((uintptr_t)mem + pad);
 
 #ifdef USE_DELETEDCHAINFLAG
   obj->_flag = DCF_alive;
@@ -77,6 +80,10 @@ allocate(size_t size, TypeHandle type_handle) {
 
   void *ptr = node_to_buffer(obj);
 
+#ifdef _DEBUG
+  assert(((uintptr_t)ptr % MemoryHook::get_memory_alignment()) == 0);
+#endif
+
 #ifdef DO_MEMORY_USAGE
   type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
 #endif  // DO_MEMORY_USAGE

+ 1 - 5
dtool/src/dtoolbase/deletedBufferChain.h

@@ -95,12 +95,8 @@ private:
   // Without DELETEDCHAINFLAG, we don't even store the _flag member at all.
   static const size_t flag_reserved_bytes = 0;
 
-#elif defined(LINMATH_ALIGN)
-  // With SSE2 alignment, we need all 16 bytes to preserve alignment.
-  static const size_t flag_reserved_bytes = 16;
-
 #else
-  // Otherwise, we only need enough space for the Integer itself.
+  // Otherwise, we need space for the integer.
   static const size_t flag_reserved_bytes = sizeof(AtomicAdjust::Integer);
 #endif  // USE_DELETEDCHAINFLAG
 

+ 1 - 1
dtool/src/dtoolbase/dtoolbase.h

@@ -374,7 +374,7 @@ typedef struct _object PyObject;
 // externally.
 #define MEMORY_HOOK_DO_ALIGN 1
 
-#elif defined(IS_OSX) || defined(_WIN64)
+#elif (defined(IS_OSX) || defined(_WIN64)) && !defined(__AVX__)
 // The OS-provided malloc implementation will do the required alignment.
 #undef MEMORY_HOOK_DO_ALIGN
 

+ 16 - 4
dtool/src/dtoolbase/memoryHook.I

@@ -43,10 +43,16 @@ get_memory_alignment() {
 #ifdef LINMATH_ALIGN
   // We require 16-byte alignment of certain structures, to support SSE2.  We
   // don't strictly have to align *everything*, but it's just easier to do so.
+#ifdef __AVX__
+  // Eigen requires 32-byte alignment when using AVX instructions.
+  const size_t alignment_size = 32;
+#else
   const size_t alignment_size = 16;
+#endif
 #else
-  // Otherwise, use word alignment.
-  const size_t alignment_size = sizeof(void *);
+  // Otherwise, align to two words.  This seems to be pretty standard to the
+  // point where some code may rely on this being the case.
+  const size_t alignment_size = sizeof(void *) * 2;
 #endif
   return alignment_size;
 }
@@ -64,7 +70,12 @@ get_header_reserved_bytes() {
 #ifdef LINMATH_ALIGN
   // If we're doing SSE2 alignment, we must reserve a full 16-byte block,
   // since anything less than that will spoil the alignment.
+#ifdef __AVX__
+  // Eigen requires 32-byte alignment when using AVX instructions.
+  static const size_t header_reserved_bytes = 32;
+#else
   static const size_t header_reserved_bytes = 16;
+#endif
 
 #elif defined(MEMORY_HOOK_DO_ALIGN)
   // If we're just aligning to words, we reserve a block as big as two words,
@@ -72,8 +83,9 @@ get_header_reserved_bytes() {
   static const size_t header_reserved_bytes = sizeof(size_t) + sizeof(size_t);
 
 #else
-  // If we're not aligning, we just need space for the word itself.
-  static const size_t header_reserved_bytes = sizeof(size_t);
+  // Virtually all allocators align to two words, so we make sure we preserve
+  // that alignment for the benefit of anyone who relies upon that.
+  static const size_t header_reserved_bytes = sizeof(void *) * 2;
 #endif
 
   return header_reserved_bytes;

+ 5 - 0
dtool/src/dtoolbase/memoryHook.cxx

@@ -53,8 +53,13 @@
 // drose: We require 16-byte alignment of certain structures, to
 // support SSE2.  We don't strictly have to align *everything*, but
 // it's just easier to do so.
+#ifdef __AVX__
+// Eigen requires 32-byte alignment when using AVX instructions.
+#define MALLOC_ALIGNMENT ((size_t)32U)
+#else
 #define MALLOC_ALIGNMENT ((size_t)16U)
 #endif
+#endif
 
 #include "dlmalloc_src.cxx"
 

+ 2 - 0
dtool/src/dtoolbase/neverFreeMemory.I

@@ -14,6 +14,8 @@
 /**
  * Returns a pointer to a newly-allocated block of memory of the indicated
  * size.
+ *
+ * Please note that the resulting pointer is not aligned to any boundary.
  */
 INLINE void *NeverFreeMemory::
 alloc(size_t size) {

+ 3 - 7
dtool/src/dtoolbase/neverFreeMemory.cxx

@@ -39,13 +39,9 @@ void *NeverFreeMemory::
 ns_alloc(size_t size) {
   _lock.acquire();
 
-  // We always allocate integer multiples of this many bytes, to guarantee
-  // this minimum alignment.
-  static const size_t alignment_size = MemoryHook::get_memory_alignment();
-
-  // Round up to the next alignment_size.
-  size = ((size + alignment_size - 1) / alignment_size) * alignment_size;
-
+  //NB: we no longer do alignment here.  The only class that uses this is
+  // DeletedBufferChain, and we can do the alignment potentially more
+  // efficiently there since we don't end up overallocating as much.
   _total_used += size;
 
   // Look for a page that has sufficient space remaining.

+ 42 - 33
dtool/src/prc/encryptStreamBuf.cxx

@@ -73,8 +73,8 @@ EncryptStreamBuf() {
   _key_length = encryption_key_length;
   _iteration_count = encryption_iteration_count;
 
-  _read_valid = false;
-  _write_valid = false;
+  _read_ctx = NULL;
+  _write_ctx = NULL;
 
   _read_overflow_buffer = NULL;
   _in_read_overflow_buffer = 0;
@@ -110,7 +110,11 @@ open_read(istream *source, bool owns_source, const string &password) {
 
   _source = source;
   _owns_source = owns_source;
-  _read_valid = false;
+
+  if (_read_ctx != NULL) {
+    EVP_CIPHER_CTX_free(_read_ctx);
+    _read_ctx = NULL;
+  }
 
   // Now read the header information.
   StreamReader sr(_source, false);
@@ -143,17 +147,21 @@ open_read(istream *source, bool owns_source, const string &password) {
 
   string iv = sr.extract_bytes(iv_length);
 
+  _read_ctx = EVP_CIPHER_CTX_new();
+  nassertv(_read_ctx != NULL);
+
   // Initialize the context
   int result;
-  result = EVP_DecryptInit(&_read_ctx, cipher, NULL, (unsigned char *)iv.data());
+  result = EVP_DecryptInit(_read_ctx, cipher, NULL, (unsigned char *)iv.data());
   nassertv(result > 0);
 
-  result = EVP_CIPHER_CTX_set_key_length(&_read_ctx, key_length);
+  result = EVP_CIPHER_CTX_set_key_length(_read_ctx, key_length);
   if (result <= 0) {
     prc_cat.error()
       << "Invalid key length " << key_length * 8 << " bits for algorithm "
       << OBJ_nid2sn(nid) << "\n";
-    EVP_CIPHER_CTX_cleanup(&_read_ctx);
+    EVP_CIPHER_CTX_free(_read_ctx);
+    _read_ctx = NULL;
     return;
   }
 
@@ -167,11 +175,9 @@ open_read(istream *source, bool owns_source, const string &password) {
   nassertv(result > 0);
 
   // Store the key within the context.
-  result = EVP_DecryptInit(&_read_ctx, NULL, key, NULL);
+  result = EVP_DecryptInit(_read_ctx, NULL, key, NULL);
   nassertv(result > 0);
 
-  _read_valid = true;
-
   _read_overflow_buffer = new unsigned char[_read_block_size];
   _in_read_overflow_buffer = 0;
   thread_consider_yield();
@@ -182,9 +188,9 @@ open_read(istream *source, bool owns_source, const string &password) {
  */
 void EncryptStreamBuf::
 close_read() {
-  if (_read_valid) {
-    EVP_CIPHER_CTX_cleanup(&_read_ctx);
-    _read_valid = false;
+  if (_read_ctx != NULL) {
+    EVP_CIPHER_CTX_free(_read_ctx);
+    _read_ctx = NULL;
   }
 
   if (_read_overflow_buffer != (unsigned char *)NULL) {
@@ -211,7 +217,6 @@ open_write(ostream *dest, bool owns_dest, const string &password) {
   close_write();
   _dest = dest;
   _owns_dest = owns_dest;
-  _write_valid = false;
 
   const EVP_CIPHER *cipher =
     EVP_get_cipherbyname(_algorithm.c_str());
@@ -220,21 +225,23 @@ open_write(ostream *dest, bool owns_dest, const string &password) {
     prc_cat.error()
       << "Unknown encryption algorithm: " << _algorithm << "\n";
     return;
-  };
+  }
 
   int nid = EVP_CIPHER_nid(cipher);
 
   int iv_length = EVP_CIPHER_iv_length(cipher);
   _write_block_size = EVP_CIPHER_block_size(cipher);
 
-  unsigned char *iv = (unsigned char *)alloca(iv_length);
-
   // Generate a random IV.  It doesn't need to be cryptographically secure,
   // just unique.
+  unsigned char *iv = (unsigned char *)alloca(iv_length);
   RAND_pseudo_bytes(iv, iv_length);
 
+  _write_ctx = EVP_CIPHER_CTX_new();
+  nassertv(_write_ctx != NULL);
+
   int result;
-  result = EVP_EncryptInit(&_write_ctx, cipher, NULL, iv);
+  result = EVP_EncryptInit(_write_ctx, cipher, NULL, iv);
   nassertv(result > 0);
 
   // Store the appropriate key length in the context.
@@ -242,12 +249,13 @@ open_write(ostream *dest, bool owns_dest, const string &password) {
   if (key_length == 0) {
     key_length = EVP_CIPHER_key_length(cipher);
   }
-  result = EVP_CIPHER_CTX_set_key_length(&_write_ctx, key_length);
+  result = EVP_CIPHER_CTX_set_key_length(_write_ctx, key_length);
   if (result <= 0) {
     prc_cat.error()
       << "Invalid key length " << key_length * 8 << " bits for algorithm "
       << OBJ_nid2sn(nid) << "\n";
-    EVP_CIPHER_CTX_cleanup(&_write_ctx);
+    EVP_CIPHER_CTX_free(_write_ctx);
+    _write_ctx = NULL;
     return;
   }
 
@@ -271,7 +279,7 @@ open_write(ostream *dest, bool owns_dest, const string &password) {
   nassertv(result > 0);
 
   // Store the key in the context.
-  result = EVP_EncryptInit(&_write_ctx, NULL, key, NULL);
+  result = EVP_EncryptInit(_write_ctx, NULL, key, NULL);
   nassertv(result > 0);
 
   // Now write the header information to the stream.
@@ -284,7 +292,6 @@ open_write(ostream *dest, bool owns_dest, const string &password) {
   sw.add_uint16((uint16_t)count);
   sw.append_data(iv, iv_length);
 
-  _write_valid = true;
   thread_consider_yield();
 }
 
@@ -298,15 +305,16 @@ close_write() {
     write_chars(pbase(), n);
     pbump(-(int)n);
 
-    if (_write_valid) {
+    if (_write_ctx != NULL) {
       unsigned char *write_buffer = (unsigned char *)alloca(_write_block_size);
       int bytes_written = 0;
-      EVP_EncryptFinal(&_write_ctx, write_buffer, &bytes_written);
+      EVP_EncryptFinal(_write_ctx, write_buffer, &bytes_written);
       thread_consider_yield();
 
       _dest->write((const char *)write_buffer, bytes_written);
 
-      _write_valid = false;
+      EVP_CIPHER_CTX_free(_write_ctx);
+      _write_ctx = NULL;
     }
 
     if (_owns_dest) {
@@ -418,7 +426,7 @@ read_chars(char *start, size_t length) {
 
   do {
     // Get more bytes from the stream.
-    if (!_read_valid) {
+    if (_read_ctx == NULL) {
       return 0;
     }
 
@@ -429,20 +437,21 @@ read_chars(char *start, size_t length) {
     int result;
     if (source_length != 0) {
       result =
-        EVP_DecryptUpdate(&_read_ctx, read_buffer, &bytes_read,
+        EVP_DecryptUpdate(_read_ctx, read_buffer, &bytes_read,
                           source_buffer, source_length);
     } else {
       result =
-        EVP_DecryptFinal(&_read_ctx, read_buffer, &bytes_read);
-      _read_valid = false;
+        EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
+      EVP_CIPHER_CTX_free(_read_ctx);
+      _read_ctx = NULL;
     }
 
     if (result <= 0) {
       prc_cat.error()
         << "Error decrypting stream.\n";
-      if (_read_valid) {
-        EVP_CIPHER_CTX_cleanup(&_read_ctx);
-        _read_valid = false;
+      if (_read_ctx != NULL) {
+        EVP_CIPHER_CTX_free(_read_ctx);
+        _read_ctx = NULL;
       }
     }
     thread_consider_yield();
@@ -472,13 +481,13 @@ read_chars(char *start, size_t length) {
  */
 void EncryptStreamBuf::
 write_chars(const char *start, size_t length) {
-  if (_write_valid && length != 0) {
+  if (_write_ctx != NULL && length != 0) {
     size_t max_write_buffer = length + _write_block_size;
     unsigned char *write_buffer = (unsigned char *)alloca(max_write_buffer);
 
     int bytes_written = 0;
     int result =
-      EVP_EncryptUpdate(&_write_ctx, write_buffer, &bytes_written,
+      EVP_EncryptUpdate(_write_ctx, write_buffer, &bytes_written,
                         (unsigned char *)start, length);
     if (result <= 0) {
       prc_cat.error()

+ 2 - 4
dtool/src/prc/encryptStreamBuf.h

@@ -64,14 +64,12 @@ private:
   int _key_length;
   int _iteration_count;
 
-  bool _read_valid;
-  EVP_CIPHER_CTX _read_ctx;
+  EVP_CIPHER_CTX *_read_ctx;
   size_t _read_block_size;
   unsigned char *_read_overflow_buffer;
   size_t _in_read_overflow_buffer;
 
-  bool _write_valid;
-  EVP_CIPHER_CTX _write_ctx;
+  EVP_CIPHER_CTX *_write_ctx;
   size_t _write_block_size;
 };
 

+ 3 - 0
dtool/src/prc/notify.cxx

@@ -343,6 +343,9 @@ assert_failure(const char *expression, int line,
   // guarantee it has already been constructed.
   ALIGN_16BYTE ConfigVariableBool assert_abort("assert-abort", false);
   if (assert_abort) {
+    // Make sure the error message has been flushed to the output.
+    nout.flush();
+
 #ifdef WIN32
     // How to trigger an exception in VC++ that offers to take us into the
     // debugger?  abort() doesn't do it.  We used to be able to assert(false),

+ 4 - 0
dtool/src/pystub/pystub.cxx

@@ -149,12 +149,14 @@ extern "C" {
   EXPCL_PYSTUB int PyUnicodeUCS2_FromStringAndSize(...);
   EXPCL_PYSTUB int PyUnicodeUCS2_FromWideChar(...);
   EXPCL_PYSTUB int PyUnicodeUCS2_AsWideChar(...);
+  EXPCL_PYSTUB int PyUnicodeUCS2_AsWideCharString(...);
   EXPCL_PYSTUB int PyUnicodeUCS2_GetSize(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_FromFormat(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_FromString(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_FromStringAndSize(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_FromWideChar(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_AsWideChar(...);
+  EXPCL_PYSTUB int PyUnicodeUCS4_AsWideCharString(...);
   EXPCL_PYSTUB int PyUnicodeUCS4_GetSize(...);
   EXPCL_PYSTUB int PyUnicode_AsUTF8(...);
   EXPCL_PYSTUB int PyUnicode_AsUTF8AndSize(...);
@@ -349,12 +351,14 @@ int PyUnicodeUCS2_FromString(...) { return 0; }
 int PyUnicodeUCS2_FromStringAndSize(...) { return 0; }
 int PyUnicodeUCS2_FromWideChar(...) { return 0; }
 int PyUnicodeUCS2_AsWideChar(...) { return 0; }
+int PyUnicodeUCS2_AsWideCharString(...) { return 0; }
 int PyUnicodeUCS2_GetSize(...) { return 0; }
 int PyUnicodeUCS4_FromFormat(...) { return 0; }
 int PyUnicodeUCS4_FromString(...) { return 0; }
 int PyUnicodeUCS4_FromStringAndSize(...) { return 0; }
 int PyUnicodeUCS4_FromWideChar(...) { return 0; }
 int PyUnicodeUCS4_AsWideChar(...) { return 0; }
+int PyUnicodeUCS4_AsWideCharString(...) { return 0; }
 int PyUnicodeUCS4_GetSize(...) { return 0; }
 int PyUnicode_AsUTF8(...) { return 0; }
 int PyUnicode_AsUTF8AndSize(...) { return 0; }

+ 4 - 0
makepanda/installer.nsi

@@ -166,6 +166,10 @@ SectionGroup "Panda3D Libraries"
         SetOutPath $INSTDIR\models
         File /r /x CVS "${BUILT}\models\*"
 
+        SetDetailsPrint both
+        DetailPrint "Installing optional components..."
+        SetDetailsPrint listonly
+
         RMDir /r "$SMPROGRAMS\${TITLE}"
         CreateDirectory "$SMPROGRAMS\${TITLE}"
     SectionEnd

+ 44 - 25
makepanda/makepanda.py

@@ -39,6 +39,7 @@ import sys
 
 COMPILER=0
 INSTALLER=0
+WHEEL=0
 GENMAN=0
 COMPRESSOR="zlib"
 THREADCOUNT=0
@@ -87,8 +88,8 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "MFC", "WX", "FLTK",                                 # Used for web plug-in only
   "ROCKET", "AWESOMIUM",                               # GUI libraries
   "CARBON", "COCOA",                                   # Mac OS X toolkits
-  "X11", "XF86DGA", "XRANDR", "XCURSOR",               # Unix platform support
-  "PANDATOOL", "PVIEW", "DEPLOYTOOLS",                 # Toolchain
+  "X11",                                               # Unix platform support
+  "PANDATOOL", "PVIEW", "DEPLOYTOOLS", "DIRECTSCRIPTS",# Toolchain
   "SKEL",                                              # Example SKEL project
   "PANDAFX",                                           # Some distortion special lenses
   "PANDAPARTICLESYSTEM",                               # Built in particle system
@@ -124,6 +125,7 @@ def usage(problem):
     print("  --verbose         (print out more information)")
     print("  --runtime         (build a runtime build instead of an SDK build)")
     print("  --installer       (build an installer)")
+    print("  --wheel           (build a pip-installable .whl)")
     print("  --optimize X      (optimization level can be 1,2,3,4)")
     print("  --version X       (set the panda version number)")
     print("  --lzma            (use lzma compression when building Windows installer)")
@@ -159,13 +161,13 @@ def usage(problem):
     os._exit(1)
 
 def parseopts(args):
-    global INSTALLER,RTDIST,RUNTIME,GENMAN,DISTRIBUTOR,VERSION
+    global INSTALLER,WHEEL,RTDIST,RUNTIME,GENMAN,DISTRIBUTOR,VERSION
     global COMPRESSOR,THREADCOUNT,OSXTARGET,OSX_ARCHS,HOST_URL
     global DEBVERSION,RPMRELEASE,GIT_COMMIT,P3DSUFFIX,RTDIST_VERSION
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
     longopts = [
         "help","distributor=","verbose","runtime","osxtarget=",
-        "optimize=","everything","nothing","installer","rtdist","nocolor",
+        "optimize=","everything","nothing","installer","wheel","rtdist","nocolor",
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "static","host=","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
@@ -188,6 +190,7 @@ def parseopts(args):
             if (option=="--help"): raise Exception
             elif (option=="--optimize"): optimize=value
             elif (option=="--installer"): INSTALLER=1
+            elif (option=="--wheel"): WHEEL=1
             elif (option=="--verbose"): SetVerbose(True)
             elif (option=="--distributor"): DISTRIBUTOR=value
             elif (option=="--rtdist"): RTDIST=1
@@ -416,9 +419,18 @@ if (RUNTIME):
 if (INSTALLER and RTDIST):
     exit("Cannot build an installer for the rtdist build!")
 
+if (WHEEL and RUNTIME):
+    exit("Cannot build a wheel for the runtime build!")
+
+if (WHEEL and RTDIST):
+    exit("Cannot build a wheel for the rtdist build!")
+
 if (INSTALLER) and (PkgSkip("PYTHON")) and (not RUNTIME) and GetTarget() == 'windows':
     exit("Cannot build installer on Windows without python")
 
+if WHEEL and PkgSkip("PYTHON"):
+    exit("Cannot build wheel without Python")
+
 if (RTDIST) and (PkgSkip("WX") and PkgSkip("FLTK")):
     exit("Cannot build rtdist without wx or fltk")
 
@@ -504,9 +516,6 @@ IncDirectory("ALWAYS", GetOutputDir()+"/include")
 
 if (COMPILER == "MSVC"):
     PkgDisable("X11")
-    PkgDisable("XRANDR")
-    PkgDisable("XF86DGA")
-    PkgDisable("XCURSOR")
     PkgDisable("GLES")
     PkgDisable("GLES2")
     PkgDisable("EGL")
@@ -796,9 +805,13 @@ if (COMPILER=="GCC"):
         SmartPkgEnable("JPEG",      "",          ("jpeg"), "jpeglib.h")
         SmartPkgEnable("PNG",       "libpng",    ("png"), "png.h", tool = "libpng-config")
 
-        if GetTarget() == "darwin" and not PkgSkip("FFMPEG"):
-            LibName("FFMPEG", "-Wl,-read_only_relocs,suppress")
-            LibName("FFMPEG", "-framework VideoDecodeAcceleration")
+        if not PkgSkip("FFMPEG"):
+            if GetTarget() == "darwin":
+                LibName("FFMPEG", "-Wl,-read_only_relocs,suppress")
+                LibName("FFMPEG", "-framework VideoDecodeAcceleration")
+            elif os.path.isfile(GetThirdpartyDir() + "ffmpeg/lib/libavcodec.a"):
+                # Needed when linking ffmpeg statically on Linux.
+                LibName("FFMPEG", "-Wl,-Bsymbolic")
 
         cv_lib = ChooseLib(("opencv_core", "cv"), "OPENCV")
         if cv_lib == "opencv_core":
@@ -828,12 +841,9 @@ if (COMPILER=="GCC"):
     if GetTarget() != 'darwin':
         # CgGL is covered by the Cg framework, and we don't need X11 components on OSX
         if not PkgSkip("NVIDIACG") and not RUNTIME:
-            SmartPkgEnable("CGGL", "", ("CgGL"), "Cg/cgGL.h")
+            SmartPkgEnable("CGGL", "", ("CgGL"), "Cg/cgGL.h", thirdparty_dir = "nvidiacg")
         if not RUNTIME:
             SmartPkgEnable("X11", "x11", "X11", ("X11", "X11/Xlib.h"))
-            SmartPkgEnable("XRANDR", "xrandr", "Xrandr", "X11/extensions/Xrandr.h")
-            SmartPkgEnable("XF86DGA", "xxf86dga", "Xxf86dga", "X11/extensions/xf86dga.h")
-            SmartPkgEnable("XCURSOR", "xcursor", "Xcursor", "X11/Xcursor/Xcursor.h")
 
     if GetHost() != "darwin":
         # Workaround for an issue where pkg-config does not include this path
@@ -1680,6 +1690,11 @@ def CompileLink(dll, obj, opts):
         if LDFLAGS != "":
             cmd += " " + LDFLAGS
 
+        # Don't link libraries with Python.
+        if "PYTHON" in opts and GetOrigExt(dll) != ".exe" and not RTDIST:
+            opts = opts[:]
+            opts.remove("PYTHON")
+
         for (opt, dir) in LIBDIRECTORIES:
             if (opt=="ALWAYS") or (opt in opts):
                 cmd += ' -L' + BracketNameWithQuotes(dir)
@@ -2182,9 +2197,6 @@ DTOOL_CONFIG=[
     ("PHAVE_STDINT_H",                 '1',                      '1'),
     ("HAVE_RTTI",                      '1',                      '1'),
     ("HAVE_X11",                       'UNDEF',                  '1'),
-    ("HAVE_XRANDR",                    'UNDEF',                  '1'),
-    ("HAVE_XF86DGA",                   'UNDEF',                  '1'),
-    ("HAVE_XCURSOR",                   'UNDEF',                  '1'),
     ("IS_LINUX",                       'UNDEF',                  '1'),
     ("IS_OSX",                         'UNDEF',                  'UNDEF'),
     ("IS_FREEBSD",                     'UNDEF',                  'UNDEF'),
@@ -2289,9 +2301,6 @@ def WriteConfigSettings():
         dtool_config["PHAVE_SYS_MALLOC_H"] = '1'
         dtool_config["HAVE_OPENAL_FRAMEWORK"] = '1'
         dtool_config["HAVE_X11"] = 'UNDEF'  # We might have X11, but we don't need it.
-        dtool_config["HAVE_XRANDR"] = 'UNDEF'
-        dtool_config["HAVE_XF86DGA"] = 'UNDEF'
-        dtool_config["HAVE_XCURSOR"] = 'UNDEF'
         dtool_config["HAVE_GLX"] = 'UNDEF'
         dtool_config["IS_LINUX"] = 'UNDEF'
         dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF'
@@ -3328,6 +3337,7 @@ if (not RUNTIME):
   TargetAdd('interrogate.exe', input='libp3cppParser.ilb')
   TargetAdd('interrogate.exe', input=COMMON_DTOOL_LIBS)
   TargetAdd('interrogate.exe', input='libp3interrogatedb.dll')
+  TargetAdd('interrogate.exe', input='libp3pystub.lib')
   TargetAdd('interrogate.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
   TargetAdd('interrogate_module_interrogate_module.obj', opts=OPTS, input='interrogate_module.cxx')
@@ -3335,6 +3345,7 @@ if (not RUNTIME):
   TargetAdd('interrogate_module.exe', input='libp3cppParser.ilb')
   TargetAdd('interrogate_module.exe', input=COMMON_DTOOL_LIBS)
   TargetAdd('interrogate_module.exe', input='libp3interrogatedb.dll')
+  TargetAdd('interrogate_module.exe', input='libp3pystub.lib')
   TargetAdd('interrogate_module.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
   if (not RTDIST):
@@ -3343,6 +3354,7 @@ if (not RUNTIME):
     TargetAdd('parse_file.exe', input='libp3cppParser.ilb')
     TargetAdd('parse_file.exe', input=COMMON_DTOOL_LIBS)
     TargetAdd('parse_file.exe', input='libp3interrogatedb.dll')
+    TargetAdd('parse_file.exe', input='libp3pystub.lib')
     TargetAdd('parse_file.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 #
@@ -3366,6 +3378,7 @@ if (not RTDIST and not RUNTIME):
   TargetAdd('test_interrogate.exe', input='test_interrogate_test_interrogate.obj')
   TargetAdd('test_interrogate.exe', input='libp3interrogatedb.dll')
   TargetAdd('test_interrogate.exe', input=COMMON_DTOOL_LIBS)
+  TargetAdd('test_interrogate.exe', input='libp3pystub.lib')
   TargetAdd('test_interrogate.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 #
@@ -4569,7 +4582,7 @@ if (GetTarget() not in ['windows', 'darwin'] and PkgSkip("GL")==0 and PkgSkip("X
   TargetAdd('libpandagl.dll', input='p3glgsg_glgsg.obj')
   TargetAdd('libpandagl.dll', input='p3glxdisplay_composite1.obj')
   TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
+  TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'X11'])
 
 #
 # DIRECTORY: panda/src/cocoadisplay/
@@ -4644,7 +4657,7 @@ if (PkgSkip("EGL")==0 and PkgSkip("GLES")==0 and PkgSkip("X11")==0 and not RUNTI
   TargetAdd('libpandagles.dll', input='p3glesgsg_glesgsg.obj')
   TargetAdd('libpandagles.dll', input='pandagles_egldisplay_composite1.obj')
   TargetAdd('libpandagles.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libpandagles.dll', opts=['MODULE', 'GLES', 'EGL', 'X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
+  TargetAdd('libpandagles.dll', opts=['MODULE', 'GLES', 'EGL', 'X11'])
 
 #
 # DIRECTORY: panda/src/egldisplay/
@@ -4662,7 +4675,7 @@ if (PkgSkip("EGL")==0 and PkgSkip("GLES2")==0 and PkgSkip("X11")==0 and not RUNT
   TargetAdd('libpandagles2.dll', input='p3gles2gsg_gles2gsg.obj')
   TargetAdd('libpandagles2.dll', input='pandagles2_egldisplay_composite1.obj')
   TargetAdd('libpandagles2.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libpandagles2.dll', opts=['MODULE', 'GLES2', 'EGL', 'X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
+  TargetAdd('libpandagles2.dll', opts=['MODULE', 'GLES2', 'EGL', 'X11'])
 
 #
 # DIRECTORY: panda/src/ode/
@@ -4958,7 +4971,7 @@ if (not RUNTIME and (GetTarget() in ('windows', 'darwin') or PkgSkip("X11")==0)
     TargetAdd('libp3tinydisplay.dll', opts=['WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM'])
   else:
     TargetAdd('libp3tinydisplay.dll', input='p3x11display_composite1.obj')
-    TargetAdd('libp3tinydisplay.dll', opts=['X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
+    TargetAdd('libp3tinydisplay.dll', opts=['X11'])
   TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite1.obj')
   TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite2.obj')
   TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_ztriangle_1.obj')
@@ -4976,7 +4989,7 @@ if (PkgSkip("DIRECT")==0):
   OPTS=['DIR:direct/src/directbase', 'PYTHON']
   TargetAdd('p3directbase_directbase.obj', opts=OPTS+['BUILDING:DIRECT'], input='directbase.cxx')
 
-  if (PkgSkip("PYTHON")==0 and not RTDIST and not RUNTIME):
+  if not PkgSkip("PYTHON") and not RTDIST and not RUNTIME and not PkgSkip("DIRECTSCRIPTS"):
     DefSymbol("BUILDING:PACKPANDA", "IMPORT_MODULE", "direct.directscripts.packpanda")
     TargetAdd('packpanda.obj', opts=OPTS+['BUILDING:PACKPANDA'], input='ppython.cxx')
     TargetAdd('packpanda.exe', input='packpanda.obj')
@@ -5151,6 +5164,7 @@ if (PkgSkip("PYTHON")==0 and PkgSkip("DIRECT")==0 and not RTDIST and not RUNTIME
   TargetAdd('p3dcparse.exe', input='dcparse_dcparse.obj')
   TargetAdd('p3dcparse.exe', input='libp3direct.dll')
   TargetAdd('p3dcparse.exe', input=COMMON_PANDA_LIBS)
+  TargetAdd('p3dcparse.exe', input='libp3pystub.lib')
   TargetAdd('p3dcparse.exe', opts=['ADVAPI', 'PYTHON'])
 
 #
@@ -7239,6 +7253,11 @@ try:
             MakeInstallerFreeBSD()
         else:
             exit("Do not know how to make an installer for this platform")
+
+    if WHEEL:
+        ProgressOutput(100.0, "Building wheel")
+        from makewheel import makewheel
+        makewheel(VERSION, GetOutputDir())
 finally:
     SaveDependencyCache()
 

+ 598 - 0
makepanda/makewheel.py

@@ -0,0 +1,598 @@
+"""
+Generates a wheel (.whl) file from the output of makepanda.
+
+Since the wheel requires special linking, this will only work if compiled with
+the `--wheel` parameter.
+
+Please keep this file work with Panda3D 1.9 until that reaches EOL.
+"""
+from __future__ import print_function, unicode_literals
+from distutils.util import get_platform
+import json
+
+import sys
+import os
+from os.path import join
+import shutil
+import zipfile
+import hashlib
+import tempfile
+import subprocess
+from distutils.sysconfig import get_config_var
+from optparse import OptionParser
+from makepandacore import ColorText, LocateBinary, ParsePandaVersion, GetExtensionSuffix, SetVerbose, GetVerbose
+from base64 import urlsafe_b64encode
+
+
+default_platform = get_platform()
+
+if default_platform.startswith("linux-"):
+    # Is this manylinux1?
+    if os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"):
+        default_platform = default_platform.replace("linux", "manylinux1")
+
+
+def get_abi_tag():
+    if sys.version_info >= (3, 0):
+        soabi = get_config_var('SOABI')
+        if soabi and soabi.startswith('cpython-'):
+            return 'cp' + soabi.split('-')[1]
+        elif soabi:
+            return soabi.replace('.', '_').replace('-', '_')
+
+    soabi = 'cp%d%d' % (sys.version_info[:2])
+
+    debug_flag = get_config_var('Py_DEBUG')
+    if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
+        soabi += 'd'
+
+    malloc_flag = get_config_var('WITH_PYMALLOC')
+    if malloc_flag is None or malloc_flag:
+        soabi += 'm'
+
+    if sys.version_info < (3, 3):
+        usize = get_config_var('Py_UNICODE_SIZE')
+        if (usize is None and sys.maxunicode == 0x10ffff) or usize == 4:
+            soabi += 'u'
+
+    return soabi
+
+
+def is_exe_file(path):
+    return os.path.isfile(path) and path.lower().endswith('.exe')
+
+
+def is_elf_file(path):
+    base = os.path.basename(path)
+    return os.path.isfile(path) and '.' not in base and \
+           open(path, 'rb').read(4) == b'\x7FELF'
+
+
+def is_mach_o_file(path):
+    base = os.path.basename(path)
+    return os.path.isfile(path) and '.' not in base and \
+           open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA',
+                                        b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
+                                        b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE')
+
+def is_fat_file(path):
+    return os.path.isfile(path) and \
+           open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA')
+
+
+if sys.platform in ('win32', 'cygwin'):
+    is_executable = is_exe_file
+elif sys.platform == 'darwin':
+    is_executable = is_mach_o_file
+else:
+    is_executable = is_elf_file
+
+
+# Other global parameters
+PY_VERSION = "cp{0}{1}".format(*sys.version_info)
+ABI_TAG = get_abi_tag()
+EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", ".sln"]
+
+# Plug-ins to install.
+PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"]
+
+WHEEL_DATA = """Wheel-Version: 1.0
+Generator: makepanda
+Root-Is-Purelib: false
+Tag: {0}-{1}-{2}
+"""
+
+METADATA = {
+    "license": "BSD",
+    "name": "Panda3D",
+    "metadata_version": "2.0",
+    "generator": "makepanda",
+    "summary": "Panda3D is a game engine, a framework for 3D rendering and "
+               "game development for Python and C++ programs.",
+    "extensions": {
+        "python.details": {
+            "project_urls": {
+                "Home": "https://www.panda3d.org/"
+            },
+            "document_names": {
+                "license": "LICENSE.txt"
+            },
+            "contacts": [
+                {
+                    "role": "author",
+                    "email": "[email protected]",
+                    "name": "Panda3D Team"
+                }
+            ]
+        }
+    },
+    "classifiers": [
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "Intended Audience :: End Users/Desktop",
+        "License :: OSI Approved :: BSD License",
+        "Operating System :: OS Independent",
+        "Programming Language :: C++",
+        "Programming Language :: Python",
+        "Topic :: Games/Entertainment",
+        "Topic :: Multimedia",
+        "Topic :: Multimedia :: Graphics",
+        "Topic :: Multimedia :: Graphics :: 3D Rendering"
+    ]
+}
+
+PANDA3D_TOOLS_INIT = """import os, sys
+import panda3d
+
+if sys.platform in ('win32', 'cygwin'):
+    path_var = 'PATH'
+elif sys.platform == 'darwin':
+    path_var = 'DYLD_LIBRARY_PATH'
+else:
+    path_var = 'LD_LIBRARY_PATH'
+
+dir = os.path.dirname(panda3d.__file__)
+del panda3d
+if not os.environ.get(path_var):
+    os.environ[path_var] = dir
+else:
+    os.environ[path_var] = dir + os.pathsep + os.environ[path_var]
+
+del os, sys, path_var, dir
+
+
+def _exec_tool(tool):
+    import os, sys
+    from subprocess import Popen
+    tools_dir = os.path.dirname(__file__)
+    handle = Popen(sys.argv, executable=os.path.join(tools_dir, tool))
+    try:
+        try:
+            return handle.wait()
+        except KeyboardInterrupt:
+            # Give the program a chance to handle the signal gracefully.
+            return handle.wait()
+    except:
+        handle.kill()
+        handle.wait()
+        raise
+
+# Register all the executables in this directory as global functions.
+{0}
+"""
+
+
+def parse_dependencies_windows(data):
+    """ Parses the given output from dumpbin /dependents to determine the list
+    of dll's this executable file depends on. """
+
+    lines = data.splitlines()
+    li = 0
+    while li < len(lines):
+        line = lines[li]
+        li += 1
+        if line.find(' has the following dependencies') != -1:
+            break
+
+    if li < len(lines):
+        line = lines[li]
+        if line.strip() == '':
+            # Skip a blank line.
+            li += 1
+
+    # Now we're finding filenames, until the next blank line.
+    filenames = []
+    while li < len(lines):
+        line = lines[li]
+        li += 1
+        line = line.strip()
+        if line == '':
+            # We're done.
+            return filenames
+        filenames.append(line)
+
+    # At least we got some data.
+    return filenames
+
+
+def parse_dependencies_unix(data):
+    """ Parses the given output from otool -XL or ldd to determine the list of
+    libraries this executable file depends on. """
+
+    lines = data.splitlines()
+    filenames = []
+    for l in lines:
+        l = l.strip()
+        if l != "statically linked":
+            filenames.append(l.split(' ', 1)[0])
+    return filenames
+
+
+def scan_dependencies(pathname):
+    """ Checks the named file for DLL dependencies, and adds any appropriate
+    dependencies found into pluginDependencies and dependentFiles. """
+
+    if sys.platform == "darwin":
+        command = ['otool', '-XL', pathname]
+    elif sys.platform in ("win32", "cygwin"):
+        command = ['dumpbin', '/dependents', pathname]
+    else:
+        command = ['ldd', pathname]
+
+    process = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)
+    output, unused_err = process.communicate()
+    retcode = process.poll()
+    if retcode:
+        raise subprocess.CalledProcessError(retcode, command[0], output=output)
+    filenames = None
+
+    if sys.platform in ("win32", "cygwin"):
+        filenames = parse_dependencies_windows(output)
+    else:
+        filenames = parse_dependencies_unix(output)
+
+    if filenames is None:
+        sys.exit("Unable to determine dependencies from %s" % (pathname))
+
+    if sys.platform == "darwin" and len(filenames) > 0:
+        # Filter out the library ID.
+        if os.path.basename(filenames[0]).split('.', 1)[0] == os.path.basename(pathname).split('.', 1)[0]:
+            del filenames[0]
+
+    return filenames
+
+
+class WheelFile(object):
+    def __init__(self, name, version, platform):
+        self.name = name
+        self.version = version
+        self.platform = platform
+
+        wheel_name = "{0}-{1}-{2}-{3}-{4}.whl".format(
+            name, version, PY_VERSION, ABI_TAG, platform)
+
+        print("Writing %s" % (wheel_name))
+        self.zip_file = zipfile.ZipFile(wheel_name, 'w', zipfile.ZIP_DEFLATED)
+        self.records = []
+
+        # Used to locate dependency libraries.
+        self.lib_path = []
+        self.dep_paths = {}
+
+    def consider_add_dependency(self, target_path, dep, search_path=None):
+        """Considers adding a dependency library.
+        Returns the target_path if it was added, which may be different from
+        target_path if it was already added earlier, or None if it wasn't."""
+
+        if dep in self.dep_paths:
+            # Already considered this.
+            return self.dep_paths[dep]
+
+        self.dep_paths[dep] = None
+
+        if dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"):
+            # Don't include the Python library.
+            return
+
+        if sys.platform == "darwin" and dep.endswith(".so"):
+            # Temporary hack for 1.9, which had link deps on modules.
+            return
+
+        source_path = None
+
+        if search_path is None:
+            search_path = self.lib_path
+
+        for lib_dir in search_path:
+            # Ignore static stuff.
+            path = os.path.join(lib_dir, dep)
+            if os.path.isfile(path):
+                source_path = os.path.normpath(path)
+                break
+
+        if not source_path:
+            # Couldn't find library in the panda3d lib dir.
+            #print("Ignoring %s" % (dep))
+            return
+
+        self.dep_paths[dep] = target_path
+        self.write_file(target_path, source_path)
+        return target_path
+
+    def write_file(self, target_path, source_path):
+        """Adds the given file to the .whl file."""
+
+        # If this is a .so file, we should set the rpath appropriately.
+        temp = None
+        ext = os.path.splitext(source_path)[1]
+        if ext in ('.so', '.dylib') or '.so.' in os.path.basename(source_path) or \
+            (not ext and is_executable(source_path)):
+            # Scan and add Unix dependencies.
+            deps = scan_dependencies(source_path)
+            for dep in deps:
+                # Only include dependencies with relative path.  Otherwise we
+                # end up overwriting system files like /lib/ld-linux.so.2!
+                # Yes, it happened to me.
+                if '/' not in dep:
+                    target_dep = os.path.dirname(target_path) + '/' + dep
+                    self.consider_add_dependency(target_dep, dep)
+
+            suffix = ''
+            if '.so' in os.path.basename(source_path):
+                suffix = '.so'
+            elif ext == '.dylib':
+                suffix = '.dylib'
+
+            temp = tempfile.NamedTemporaryFile(suffix=suffix, prefix='whl', delete=False)
+
+            # On macOS, if no fat wheel was requested, extract the right architecture.
+            if sys.platform == "darwin" and is_fat_file(source_path) and not self.platform.endswith("_intel"):
+                if self.platform.endswith("_x86_64"):
+                    arch = 'x86_64'
+                else:
+                    arch = self.platform.split('_')[-1]
+                subprocess.call(['lipo', source_path, '-extract', arch, '-output', temp.name])
+            else:
+                # 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()
+
+            # Fix things like @loader_path/../lib references
+            if sys.platform == "darwin":
+                loader_path = [os.path.dirname(source_path)]
+                for dep in deps:
+                    if '@loader_path' not in dep:
+                        continue
+
+                    dep_path = dep.replace('@loader_path', '.')
+                    target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
+                    target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path)
+                    if not target_dep:
+                        # It won't be included, so no use adjusting the path.
+                        continue
+
+                    new_dep = os.path.join('@loader_path', os.path.relpath(target_dep, os.path.dirname(target_path)))
+                    subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name])
+            else:
+                subprocess.call(["strip", "-s", temp.name])
+                subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name])
+
+            source_path = temp.name
+
+        ext = ext.lower()
+        if ext in ('.dll', '.pyd', '.exe'):
+            # Scan and add Win32 dependencies.
+            for dep in scan_dependencies(source_path):
+                target_dep = os.path.dirname(target_path) + '/' + dep
+                self.consider_add_dependency(target_dep, dep)
+
+        # Calculate the SHA-256 hash and size.
+        sha = hashlib.sha256()
+        fp = open(source_path, 'rb')
+        size = 0
+        data = fp.read(1024 * 1024)
+        while data:
+            size += len(data)
+            sha.update(data)
+            data = fp.read(1024 * 1024)
+        fp.close()
+
+        # Save it in PEP-0376 format for writing out later.
+        digest = str(urlsafe_b64encode(sha.digest()))
+        digest = digest.rstrip('=')
+        self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, size))
+
+        if GetVerbose():
+            print("Adding %s from %s" % (target_path, source_path))
+        self.zip_file.write(source_path, target_path)
+
+        #if temp:
+        #    os.unlink(temp.name)
+
+    def write_file_data(self, target_path, source_data):
+        """Adds the given file from a string."""
+
+        sha = hashlib.sha256()
+        sha.update(source_data.encode())
+        digest = str(urlsafe_b64encode(sha.digest()))
+        digest = digest.rstrip('=')
+        self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, len(source_data)))
+
+        if GetVerbose():
+            print("Adding %s from data" % target_path)
+        self.zip_file.writestr(target_path, source_data)
+
+    def write_directory(self, target_dir, source_dir):
+        """Adds the given directory recursively to the .whl file."""
+
+        for root, dirs, files in os.walk(source_dir):
+            for file in files:
+                if os.path.splitext(file)[1] in EXCLUDE_EXT:
+                    continue
+
+                source_path = os.path.join(root, file)
+                target_path = os.path.join(target_dir, os.path.relpath(source_path, source_dir))
+                target_path = target_path.replace('\\', '/')
+                self.write_file(target_path, source_path)
+
+    def close(self):
+        # Write the RECORD file.
+        record_file = "{0}-{1}.dist-info/RECORD".format(self.name, self.version)
+        self.records.append(record_file + ",,\n")
+
+        self.zip_file.writestr(record_file, "".join(self.records))
+        self.zip_file.close()
+
+
+def makewheel(version, output_dir, platform=default_platform):
+    if sys.platform not in ("win32", "darwin") and not sys.platform.startswith("cygwin"):
+        if not LocateBinary("patchelf"):
+            raise Exception("patchelf is required when building a Linux wheel.")
+
+    platform = platform.replace('-', '_').replace('.', '_')
+
+    # Global filepaths
+    panda3d_dir = join(output_dir, "panda3d")
+    pandac_dir = join(output_dir, "pandac")
+    direct_dir = join(output_dir, "direct")
+    models_dir = join(output_dir, "models")
+    etc_dir = join(output_dir, "etc")
+    bin_dir = join(output_dir, "bin")
+    if sys.platform == "win32":
+        libs_dir = join(output_dir, "bin")
+    else:
+        libs_dir = join(output_dir, "lib")
+    license_src = "LICENSE"
+    readme_src = "README.md"
+
+    # Update relevant METADATA entries
+    METADATA['version'] = version
+    version_classifiers = [
+        "Programming Language :: Python :: {0}".format(*sys.version_info),
+        "Programming Language :: Python :: {0}.{1}".format(*sys.version_info),
+    ]
+    METADATA['classifiers'].extend(version_classifiers)
+
+    # Build out the metadata
+    details = METADATA["extensions"]["python.details"]
+    homepage = details["project_urls"]["Home"]
+    author = details["contacts"][0]["name"]
+    email = details["contacts"][0]["email"]
+    metadata = ''.join([
+        "Metadata-Version: {metadata_version}\n" \
+        "Name: {name}\n" \
+        "Version: {version}\n" \
+        "Summary: {summary}\n" \
+        "License: {license}\n".format(**METADATA),
+        "Home-page: {0}\n".format(homepage),
+        "Author: {0}\n".format(author),
+        "Author-email: {0}\n".format(email),
+        "Platform: {0}\n".format(platform),
+    ] + ["Classifier: {0}\n".format(c) for c in METADATA['classifiers']])
+
+    # Zip it up and name it the right thing
+    whl = WheelFile('panda3d', version, platform)
+    whl.lib_path = [libs_dir]
+
+    # Add the trees with Python modules.
+    whl.write_directory('direct', direct_dir)
+
+    # Write the panda3d tree.  We use a custom empty __init__ since the
+    # default one adds the bin directory to the PATH, which we don't have.
+    whl.write_file_data('panda3d/__init__.py', '')
+
+    ext_suffix = GetExtensionSuffix()
+
+    for file in os.listdir(panda3d_dir):
+        if file == '__init__.py':
+            pass
+        elif file.endswith(ext_suffix) or file.endswith('.py'):
+            source_path = os.path.join(panda3d_dir, file)
+
+            if file.endswith('.pyd') and platform.startswith('cygwin'):
+                # Rename it to .dll for cygwin Python to be able to load it.
+                target_path = 'panda3d/' + os.path.splitext(file)[0] + '.dll'
+            else:
+                target_path = 'panda3d/' + file
+
+            whl.write_file(target_path, source_path)
+
+    # Add plug-ins.
+    for lib in PLUGIN_LIBS:
+        plugin_name = 'lib' + lib
+        if sys.platform in ('win32', 'cygwin'):
+            plugin_name += '.dll'
+        elif sys.platform == 'darwin':
+            plugin_name += '.dylib'
+        else:
+            plugin_name += '.so'
+        plugin_path = os.path.join(libs_dir, plugin_name)
+        if os.path.isfile(plugin_path):
+            whl.write_file('panda3d/' + plugin_name, plugin_path)
+
+    # Add the .data directory, containing additional files.
+    data_dir = 'panda3d-{0}.data'.format(version)
+    #whl.write_directory(data_dir + '/data/etc', etc_dir)
+    #whl.write_directory(data_dir + '/data/models', models_dir)
+
+    # Actually, let's not.  That seems to install the files to the strangest
+    # places in the user's filesystem.  Let's instead put them in panda3d.
+    whl.write_directory('panda3d/etc', etc_dir)
+    whl.write_directory('panda3d/models', models_dir)
+
+    # Add the pandac tree for backward compatibility.
+    for file in os.listdir(pandac_dir):
+        if file.endswith('.py'):
+            whl.write_file('pandac/' + file, os.path.join(pandac_dir, file))
+
+    # Add a panda3d-tools directory containing the executables.
+    entry_points = '[console_scripts]\n'
+    entry_points += 'eggcacher = direct.directscripts.eggcacher:main\n'
+    entry_points += 'packpanda = direct.directscripts.packpanda:main\n'
+    tools_init = ''
+    for file in os.listdir(bin_dir):
+        basename = os.path.splitext(file)[0]
+        if basename in ('eggcacher', 'packpanda'):
+            continue
+
+        source_path = os.path.join(bin_dir, file)
+
+        if is_executable(source_path):
+            # Put the .exe files inside the panda3d-tools directory.
+            whl.write_file('panda3d_tools/' + file, source_path)
+
+            # Tell pip to create a wrapper script.
+            funcname = basename.replace('-', '_')
+            entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname)
+            tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file)
+
+    whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init))
+
+    # Add the dist-info directory last.
+    info_dir = 'panda3d-{0}.dist-info'.format(version)
+    whl.write_file_data(info_dir + '/entry_points.txt', entry_points)
+    whl.write_file_data(info_dir + '/metadata.json', json.dumps(METADATA, indent=4, separators=(',', ': ')))
+    whl.write_file_data(info_dir + '/METADATA', metadata)
+    whl.write_file_data(info_dir + '/WHEEL', WHEEL_DATA.format(PY_VERSION, ABI_TAG, platform))
+    whl.write_file(info_dir + '/LICENSE.txt', license_src)
+    whl.write_file(info_dir + '/README.md', readme_src)
+    whl.write_file_data(info_dir + '/top_level.txt', 'direct\npanda3d\npandac\npanda3d_tools\n')
+
+    whl.close()
+
+
+if __name__ == "__main__":
+    version = ParsePandaVersion("dtool/PandaVersion.pp")
+
+    parser = OptionParser()
+    parser.add_option('', '--version', dest = 'version', help = 'Panda3D version number (default: %s)' % (version), default = version)
+    parser.add_option('', '--outputdir', dest = 'outputdir', help = 'Makepanda\'s output directory (default: built)', default = 'built')
+    parser.add_option('', '--verbose', dest = 'verbose', help = 'Enable verbose output', action = 'store_true', default = False)
+    parser.add_option('', '--platform', dest = 'platform', help = 'Override platform tag (default: %s)' % (default_platform), default = get_platform())
+    (options, args) = parser.parse_args()
+
+    SetVerbose(options.verbose)
+    makewheel(options.version, options.outputdir, options.platform)

+ 14 - 7
panda/src/cocoadisplay/cocoaGraphicsPipe.mm

@@ -140,11 +140,14 @@ load_display_information() {
   // _display_information->_device_id = CGDisplaySerialNumber(_display);
 
   // Display modes
+  size_t num_modes = 0;
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, NULL);
-  size_t num_modes = CFArrayGetCount(modes);
-  _display_information->_total_display_modes = num_modes;
-  _display_information->_display_mode_array = new DisplayMode[num_modes];
+  if (modes != NULL) {
+    num_modes = CFArrayGetCount(modes);
+    _display_information->_total_display_modes = num_modes;
+    _display_information->_display_mode_array = new DisplayMode[num_modes];
+  }
 
   for (size_t i = 0; i < num_modes; ++i) {
     CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
@@ -181,13 +184,17 @@ load_display_information() {
     }
     CFRelease(encoding);
   }
-  CFRelease(modes);
+  if (modes != NULL) {
+    CFRelease(modes);
+  }
 
 #else
   CFArrayRef modes = CGDisplayAvailableModes(_display);
-  size_t num_modes = CFArrayGetCount(modes);
-  _display_information->_total_display_modes = num_modes;
-  _display_information->_display_mode_array = new DisplayMode[num_modes];
+  if (modes != NULL) {
+    num_modes = CFArrayGetCount(modes);
+    _display_information->_total_display_modes = num_modes;
+    _display_information->_display_mode_array = new DisplayMode[num_modes];
+  }
 
   for (size_t i = 0; i < num_modes; ++i) {
     CFDictionaryRef mode = (CFDictionaryRef) CFArrayGetValueAtIndex(modes, i);

+ 0 - 13
panda/src/display/get_x11.h

@@ -49,19 +49,6 @@ struct XVisualInfo;
 #include <X11/Xutil.h>
 #include <X11/keysym.h>
 #include <X11/Xatom.h>
-
-#ifdef HAVE_XRANDR
-#include <X11/extensions/Xrandr.h>
-#endif  // HAVE_XRANDR
-
-#ifdef HAVE_XCURSOR
-#include <X11/Xcursor/Xcursor.h>
-#endif
-
-#ifdef HAVE_XF86DGA
-#include <X11/extensions/Xxf86dga.h>
-#endif
-
 #include "post_x11_include.h"
 
 #endif  // CPPPARSER

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

@@ -62,6 +62,14 @@ release_all_index_buffers() {
   return _prepared_objects->release_all_index_buffers();
 }
 
+/**
+ * Frees the resources for all index buffers associated with this GSG.
+ */
+INLINE int GraphicsStateGuardian::
+release_all_shader_buffers() {
+  return _prepared_objects->release_all_shader_buffers();
+}
+
 /**
  * Sets the active flag associated with the GraphicsStateGuardian.  If the
  * GraphicsStateGuardian is marked inactive, nothing is rendered.  This is not

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

@@ -62,12 +62,15 @@
 #include <algorithm>
 #include <limits.h>
 
-PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Vertex buffer switch:Vertex");
-PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Vertex buffer switch:Index");
+PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Buffer switch:Vertex");
+PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Buffer switch:Index");
+PStatCollector GraphicsStateGuardian::_shader_buffer_switch_pcollector("Buffer switch:Shader");
 PStatCollector GraphicsStateGuardian::_load_vertex_buffer_pcollector("Draw:Transfer data:Vertex buffer");
 PStatCollector GraphicsStateGuardian::_load_index_buffer_pcollector("Draw:Transfer data:Index buffer");
+PStatCollector GraphicsStateGuardian::_load_shader_buffer_pcollector("Draw:Transfer data:Shader buffer");
 PStatCollector GraphicsStateGuardian::_create_vertex_buffer_pcollector("Draw:Transfer data:Create Vertex buffer");
 PStatCollector GraphicsStateGuardian::_create_index_buffer_pcollector("Draw:Transfer data:Create Index buffer");
+PStatCollector GraphicsStateGuardian::_create_shader_buffer_pcollector("Draw:Transfer data:Create Shader buffer");
 PStatCollector GraphicsStateGuardian::_load_texture_pcollector("Draw:Transfer data:Texture");
 PStatCollector GraphicsStateGuardian::_data_transferred_pcollector("Data transferred");
 PStatCollector GraphicsStateGuardian::_texmgrmem_total_pcollector("Texture manager");
@@ -104,6 +107,7 @@ PStatCollector GraphicsStateGuardian::_prepare_geom_pcollector("Draw:Prepare:Geo
 PStatCollector GraphicsStateGuardian::_prepare_shader_pcollector("Draw:Prepare:Shader");
 PStatCollector GraphicsStateGuardian::_prepare_vertex_buffer_pcollector("Draw:Prepare:Vertex buffer");
 PStatCollector GraphicsStateGuardian::_prepare_index_buffer_pcollector("Draw:Prepare:Index buffer");
+PStatCollector GraphicsStateGuardian::_prepare_shader_buffer_pcollector("Draw:Prepare:Shader buffer");
 
 PStatCollector GraphicsStateGuardian::_draw_set_state_transform_pcollector("Draw:Set State:Transform");
 PStatCollector GraphicsStateGuardian::_draw_set_state_alpha_test_pcollector("Draw:Set State:Alpha test");
@@ -657,6 +661,22 @@ void GraphicsStateGuardian::
 release_index_buffer(IndexBufferContext *) {
 }
 
+/**
+ * Prepares the indicated buffer for retained-mode rendering.
+ */
+BufferContext *GraphicsStateGuardian::
+prepare_shader_buffer(ShaderBuffer *) {
+  return (BufferContext *)NULL;
+}
+
+/**
+ * Frees the resources previously allocated via a call to prepare_data(),
+ * including deleting the BufferContext itself, if necessary.
+ */
+void GraphicsStateGuardian::
+release_shader_buffer(BufferContext *) {
+}
+
 /**
  * Begins a new occlusion query.  After this call, you may call
  * begin_draw_primitives() and draw_triangles()/draw_whatever() repeatedly.

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

@@ -88,6 +88,7 @@ PUBLISHED:
   INLINE int release_all_geoms();
   INLINE int release_all_vertex_buffers();
   INLINE int release_all_index_buffers();
+  INLINE int release_all_shader_buffers();
 
   INLINE void set_active(bool active);
   INLINE bool is_active() const;
@@ -307,6 +308,9 @@ public:
   virtual IndexBufferContext *prepare_index_buffer(GeomPrimitive *data);
   virtual void release_index_buffer(IndexBufferContext *ibc);
 
+  virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data);
+  virtual void release_shader_buffer(BufferContext *ibc);
+
   virtual void begin_occlusion_query();
   virtual PT(OcclusionQueryContext) end_occlusion_query();
 
@@ -640,10 +644,13 @@ public:
   // Statistics
   static PStatCollector _vertex_buffer_switch_pcollector;
   static PStatCollector _index_buffer_switch_pcollector;
+  static PStatCollector _shader_buffer_switch_pcollector;
   static PStatCollector _load_vertex_buffer_pcollector;
   static PStatCollector _load_index_buffer_pcollector;
+  static PStatCollector _load_shader_buffer_pcollector;
   static PStatCollector _create_vertex_buffer_pcollector;
   static PStatCollector _create_index_buffer_pcollector;
+  static PStatCollector _create_shader_buffer_pcollector;
   static PStatCollector _load_texture_pcollector;
   static PStatCollector _data_transferred_pcollector;
   static PStatCollector _texmgrmem_total_pcollector;
@@ -680,6 +687,7 @@ public:
   static PStatCollector _prepare_shader_pcollector;
   static PStatCollector _prepare_vertex_buffer_pcollector;
   static PStatCollector _prepare_index_buffer_pcollector;
+  static PStatCollector _prepare_shader_buffer_pcollector;
 
   // A whole slew of collectors to measure the cost of individual state
   // changes.  These are disabled by default.

+ 17 - 7
panda/src/downloader/httpClient.cxx

@@ -231,11 +231,13 @@ operator = (const HTTPClient &copy) {
  */
 HTTPClient::
 ~HTTPClient() {
-  // Before we can free the context, we must remove the X509_STORE pointer
-  // from it, so it won't be destroyed along with it (this object is shared
-  // among all contexts).
   if (_ssl_ctx != (SSL_CTX *)NULL) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+    // Before we can free the context, we must remove the X509_STORE pointer
+    // from it, so it won't be destroyed along with it (this object is shared
+    // among all contexts).
     _ssl_ctx->cert_store = NULL;
+#endif
     SSL_CTX_free(_ssl_ctx);
   }
 
@@ -1122,7 +1124,13 @@ get_ssl_ctx() {
   OpenSSLWrapper *sslw = OpenSSLWrapper::get_global_ptr();
   sslw->notify_ssl_errors();
 
-  SSL_CTX_set_cert_store(_ssl_ctx, sslw->get_x509_store());
+  X509_STORE *store = sslw->get_x509_store();
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+  if (store != NULL) {
+    X509_STORE_up_ref(store);
+  }
+#endif
+  SSL_CTX_set_cert_store(_ssl_ctx, store);
 
   return _ssl_ctx;
 }
@@ -1511,15 +1519,17 @@ x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) {
   for (int ai = 0; ai < count_a; ai++) {
     X509_NAME_ENTRY *na = X509_NAME_get_entry(name_a, ai);
 
-    int bi = X509_NAME_get_index_by_OBJ(name_b, na->object, -1);
+    int bi = X509_NAME_get_index_by_OBJ(name_b, X509_NAME_ENTRY_get_object(na), -1);
     if (bi < 0) {
       // This entry in name_a is not defined in name_b.
       return false;
     }
 
     X509_NAME_ENTRY *nb = X509_NAME_get_entry(name_b, bi);
-    if (na->value->length != nb->value->length ||
-        memcmp(na->value->data, nb->value->data, na->value->length) != 0) {
+    ASN1_STRING *na_value = X509_NAME_ENTRY_get_data(na);
+    ASN1_STRING *nb_value = X509_NAME_ENTRY_get_data(nb);
+    if (na_value->length != nb_value->length ||
+        memcmp(na_value->data, nb_value->data, na_value->length) != 0) {
       // This entry in name_a doesn't match that of name_b.
       return false;
     }

+ 8 - 0
panda/src/ffmpeg/config_ffmpeg.cxx

@@ -76,6 +76,14 @@ ConfigVariableInt ffmpeg_read_buffer_size
           "This is important for performance.  A typical size is that of a "
           "cache page, e.g. 4kb."));
 
+ConfigVariableBool ffmpeg_prefer_libvpx
+("ffmpeg-prefer-libvpx", false,
+ PRC_DESC("If this is true, Panda will overrule ffmpeg's best judgment on "
+          "which decoder to use for decoding VP8 and VP9 files, and try to "
+          "choose libvpx.  This is useful when you want to play WebM videos "
+          "with an alpha channel, which aren't supported by ffmpeg's own "
+          "VP8/VP9 decoders."));
+
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be

+ 1 - 0
panda/src/ffmpeg/config_ffmpeg.h

@@ -31,6 +31,7 @@ extern ConfigVariableBool ffmpeg_support_seek;
 extern ConfigVariableBool ffmpeg_global_lock;
 extern ConfigVariableEnum<ThreadPriority> ffmpeg_thread_priority;
 extern ConfigVariableInt ffmpeg_read_buffer_size;
+extern ConfigVariableBool ffmpeg_prefer_libvpx;
 
 extern EXPCL_FFMPEG void init_libffmpeg();
 

+ 48 - 18
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -22,6 +22,7 @@
 extern "C" {
   #include "libavcodec/avcodec.h"
   #include "libavformat/avformat.h"
+  #include "libavutil/pixdesc.h"
 #ifdef HAVE_SWSCALE
   #include "libswscale/swscale.h"
 #endif
@@ -35,11 +36,21 @@ PStatCollector FfmpegVideoCursor::_fetch_buffer_pcollector("*:FFMPEG Video Decod
 PStatCollector FfmpegVideoCursor::_seek_pcollector("*:FFMPEG Video Decoding:Seek");
 PStatCollector FfmpegVideoCursor::_export_frame_pcollector("*:FFMPEG Convert Video to BGR");
 
-
 #if LIBAVFORMAT_VERSION_MAJOR < 53
   #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
 #endif
 
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 74, 100)
+#define AV_PIX_FMT_NONE PIX_FMT_NONE
+#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
+#define AV_PIX_FMT_BGRA PIX_FMT_BGRA
+typedef PixelFormat AVPixelFormat;
+#endif
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 32, 100)
+#define AV_PIX_FMT_FLAG_ALPHA PIX_FMT_ALPHA
+#endif
+
 /**
  * This constructor is only used when reading from a bam file.
  */
@@ -55,6 +66,7 @@ FfmpegVideoCursor() :
   _format_ctx(NULL),
   _video_ctx(NULL),
   _convert_ctx(NULL),
+  _pixel_format((int)AV_PIX_FMT_NONE),
   _video_index(-1),
   _frame(NULL),
   _frame_out(NULL),
@@ -80,17 +92,6 @@ init_from(FfmpegVideo *source) {
 
   ReMutexHolder av_holder(_av_lock);
 
-#ifdef HAVE_SWSCALE
-  nassertv(_convert_ctx == NULL);
-  _convert_ctx = sws_getContext(_size_x, _size_y, _video_ctx->pix_fmt,
-#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 74, 100)
-                                _size_x, _size_y, AV_PIX_FMT_BGR24,
-#else
-                                _size_x, _size_y, PIX_FMT_BGR24,
-#endif
-                                SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
-#endif  // HAVE_SWSCALE
-
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
   _frame = av_frame_alloc();
   _frame_out = av_frame_alloc();
@@ -115,6 +116,25 @@ init_from(FfmpegVideo *source) {
   _eof_known = false;
   _eof_frame = 0;
 
+  // Check if we got an alpha format.  Please note that some video codecs
+  // (eg. libvpx) change the pix_fmt after decoding the first frame, which is
+  // why we didn't do this earlier.
+  const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(_video_ctx->pix_fmt);
+  if (desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA) != 0) {
+    _num_components = 4;
+    _pixel_format = (int)AV_PIX_FMT_BGRA;
+  } else {
+    _num_components = 3;
+    _pixel_format = (int)AV_PIX_FMT_BGR24;
+  }
+
+#ifdef HAVE_SWSCALE
+  nassertv(_convert_ctx == NULL);
+  _convert_ctx = sws_getContext(_size_x, _size_y, _video_ctx->pix_fmt,
+                                _size_x, _size_y, (AVPixelFormat)_pixel_format,
+                                SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
+#endif  // HAVE_SWSCALE
+
 #ifdef HAVE_THREADS
   set_max_readahead_frames(ffmpeg_max_readahead_frames);
 #endif  // HAVE_THREADS
@@ -495,7 +515,17 @@ open_stream() {
     return false;
   }
 
-  AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
+  AVCodec *pVideoCodec = NULL;
+  if (ffmpeg_prefer_libvpx) {
+    if ((int)_video_ctx->codec_id == 168) { // AV_CODEC_ID_VP9
+      pVideoCodec = avcodec_find_decoder_by_name("libvpx-vp9");
+    } else if (_video_ctx->codec_id == AV_CODEC_ID_VP8) {
+      pVideoCodec = avcodec_find_decoder_by_name("libvpx");
+    }
+  }
+  if (pVideoCodec == NULL) {
+    pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
+  }
   if (pVideoCodec == NULL) {
     ffmpeg_cat.info()
       << "Couldn't find codec\n";
@@ -515,7 +545,7 @@ open_stream() {
 
   _size_x = _video_ctx->width;
   _size_y = _video_ctx->height;
-  _num_components = 3; // Don't know how to implement RGBA movies yet.
+  _num_components = 3;
   _length = (double)_format_ctx->duration / (double)AV_TIME_BASE;
   _can_seek = true;
   _can_seek_fast = true;
@@ -1075,8 +1105,8 @@ export_frame(FfmpegBuffer *buffer) {
     return;
   }
 
-  _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
-  _frame_out->linesize[0] = _size_x * -3;
+  _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * _num_components);
+  _frame_out->linesize[0] = _size_x * -_num_components;
   buffer->_begin_frame = _begin_frame;
   buffer->_end_frame = _end_frame;
 
@@ -1086,7 +1116,7 @@ export_frame(FfmpegBuffer *buffer) {
     nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
     sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
 #else
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
+    img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
                 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
 #endif
   } else {
@@ -1094,7 +1124,7 @@ export_frame(FfmpegBuffer *buffer) {
     nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
     sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
 #else
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
+    img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
                 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
 #endif
   }

+ 2 - 0
panda/src/ffmpeg/ffmpegVideoCursor.h

@@ -105,6 +105,8 @@ private:
   ThreadPriority _thread_priority;
   PT(GenericThread) _thread;
 
+  int _pixel_format;
+
   // This global Mutex protects calls to avcodec_opencloseetc.
   static ReMutex _av_lock;
 

+ 25 - 0
panda/src/glstuff/glBufferContext_src.I

@@ -0,0 +1,25 @@
+/**
+ * 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 glBufferContext_src.I
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+/**
+ *
+ */
+INLINE CLP(BufferContext)::
+CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg,
+                   PreparedGraphicsObjects *pgo) :
+  BufferContext(&pgo->_sbuffer_residency),
+  AdaptiveLruPage(0),
+  _glgsg(glgsg)
+{
+  _index = 0;
+}

+ 49 - 0
panda/src/glstuff/glBufferContext_src.cxx

@@ -0,0 +1,49 @@
+/**
+ * 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 glBufferContext_src.cxx
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+TypeHandle CLP(BufferContext)::_type_handle;
+
+/**
+ * Evicts the page from the LRU.  Called internally when the LRU determines
+ * that it is full.  May also be called externally when necessary to
+ * explicitly evict the page.
+ *
+ * It is legal for this method to either evict the page as requested, do
+ * nothing (in which case the eviction will be requested again at the next
+ * epoch), or requeue itself on the tail of the queue (in which case the
+ * eviction will be requested again much later).
+ */
+void CLP(BufferContext)::
+evict_lru() {
+  dequeue_lru();
+
+  // Make sure the buffer is unbound before we delete it.
+  if (_glgsg->_current_ibuffer_index == _index) {
+    if (GLCAT.is_debug() && gl_debug_buffers) {
+      GLCAT.debug()
+        << "unbinding index buffer\n";
+    }
+    _glgsg->_glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    _glgsg->_current_ibuffer_index = 0;
+  }
+
+  // Free the buffer.
+  _glgsg->_glDeleteBuffers(1, &_index);
+
+  // We still need a valid index number, though, in case we want to re-load
+  // the buffer later.
+  _glgsg->_glGenBuffers(1, &_index);
+
+  update_data_size_bytes(0);
+  set_resident(false);
+}

+ 52 - 0
panda/src/glstuff/glBufferContext_src.h

@@ -0,0 +1,52 @@
+/**
+ * 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 glBufferContext_src.h
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+#include "pandabase.h"
+#include "bufferContext.h"
+#include "deletedChain.h"
+
+/**
+ * Caches a GeomPrimitive on the GL as a buffer object.
+ */
+class EXPCL_GL CLP(BufferContext) : public BufferContext, public AdaptiveLruPage {
+public:
+  INLINE CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg,
+                            PreparedGraphicsObjects *pgo);
+  ALLOC_DELETED_CHAIN(CLP(BufferContext));
+
+  virtual void evict_lru();
+
+  CLP(GraphicsStateGuardian) *_glgsg;
+
+  // This is the GL "name" of the data object.
+  GLuint _index;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    BufferContext::init_type();
+    register_type(_type_handle, CLASSPREFIX_QUOTED "BufferContext",
+                  BufferContext::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 "glBufferContext_src.I"

+ 160 - 9
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1796,7 +1796,7 @@ reset() {
   }
 #endif
 
-#ifndef OPENGLES
+#ifndef OPENGLES_1
   // Check for uniform buffers.
 #ifdef OPENGLES
   if (is_at_least_gl_version(3, 1) || has_extension("GL_ARB_uniform_buffer_object")) {
@@ -1810,11 +1810,29 @@ reset() {
        get_extension_func("glGetActiveUniformBlockiv");
     _glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)
        get_extension_func("glGetActiveUniformBlockName");
+  } else {
+    _supports_uniform_buffers = false;
+  }
+
+#ifndef OPENGLES
+  // Check for SSBOs.
+  if (is_at_least_gl_version(4, 3) || has_extension("ARB_shader_storage_buffer_object")) {
+    _supports_shader_buffers = true;
+    _glGetProgramInterfaceiv = (PFNGLGETPROGRAMINTERFACEIVPROC)
+       get_extension_func("glGetProgramInterfaceiv");
+    _glGetProgramResourceName = (PFNGLGETPROGRAMRESOURCENAMEPROC)
+       get_extension_func("glGetProgramResourceName");
+    _glGetProgramResourceiv = (PFNGLGETPROGRAMRESOURCEIVPROC)
+       get_extension_func("glGetProgramResourceiv");
+  } else
+#endif
+  {
+    _supports_shader_buffers = false;
+  }
 
+  if (_supports_uniform_buffers || _supports_shader_buffers) {
     _glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)
       get_extension_func("glBindBufferBase");
-  } else {
-    _supports_uniform_buffers = false;
   }
 #endif
 
@@ -3031,6 +3049,9 @@ reset() {
   _current_vertex_buffers.clear();
   _current_vertex_format.clear();
   memset(_vertex_attrib_columns, 0, sizeof(const GeomVertexColumn *) * 32);
+
+  _current_sbuffer_index = 0;
+  _current_sbuffer_base.clear();
 #endif
 
   report_my_gl_errors();
@@ -5873,6 +5894,122 @@ setup_primitive(const unsigned char *&client_pointer,
   return true;
 }
 
+#ifndef OPENGLES
+/**
+ * Creates a new retained-mode representation of the given data, and returns a
+ * newly-allocated BufferContext pointer to reference it.  It is the
+ * responsibility of the calling function to later call release_shader_buffer()
+ * with this same pointer (which will also delete the pointer).
+ *
+ * This function should not be called directly to prepare a buffer.  Instead,
+ * call ShaderBuffer::prepare().
+ */
+BufferContext *CLP(GraphicsStateGuardian)::
+prepare_shader_buffer(ShaderBuffer *data) {
+  if (_supports_shader_buffers) {
+    PStatGPUTimer timer(this, _prepare_shader_buffer_pcollector);
+
+    CLP(BufferContext) *gbc = new CLP(BufferContext)(this, _prepared_objects);
+    _glGenBuffers(1, &gbc->_index);
+
+    if (GLCAT.is_debug() && gl_debug_buffers) {
+      GLCAT.debug()
+        << "creating shader buffer " << (int)gbc->_index << ": "<< *data << "\n";
+    }
+    _glBindBuffer(GL_SHADER_STORAGE_BUFFER, gbc->_index);
+    _current_sbuffer_index = gbc->_index;
+
+    if (_use_object_labels) {
+      string name = data->get_name();
+      _glObjectLabel(GL_SHADER_STORAGE_BUFFER, gbc->_index, name.size(), name.data());
+    }
+
+    uint64_t num_bytes = data->get_data_size_bytes();
+    if (_supports_buffer_storage) {
+      _glBufferStorage(GL_SHADER_STORAGE_BUFFER, num_bytes, data->get_initial_data(), 0);
+    } else {
+      _glBufferData(GL_SHADER_STORAGE_BUFFER, num_bytes, data->get_initial_data(), get_usage(data->get_usage_hint()));
+    }
+
+    gbc->enqueue_lru(&_prepared_objects->_graphics_memory_lru);
+
+    report_my_gl_errors();
+    return gbc;
+  }
+
+  return NULL;
+}
+
+/**
+ * Binds the given shader buffer to the given binding slot.
+ */
+void CLP(GraphicsStateGuardian)::
+apply_shader_buffer(GLuint base, ShaderBuffer *buffer) {
+  GLuint index = 0;
+  if (buffer != NULL) {
+    BufferContext *bc = buffer->prepare_now(get_prepared_objects(), this);
+    if (bc != NULL) {
+      CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc);
+      index = gbc->_index;
+      gbc->set_active(true);
+    }
+  }
+
+  if (base >= _current_sbuffer_base.size()) {
+    _current_sbuffer_base.resize(base + 1, 0);
+  }
+
+  if (_current_sbuffer_base[base] != index) {
+    if (GLCAT.is_spam() && gl_debug_buffers) {
+      GLCAT.spam()
+        << "binding shader buffer " << (int)index
+        << " to index " << base << "\n";
+    }
+    _glBindBufferBase(GL_SHADER_STORAGE_BUFFER, base, index);
+    _current_sbuffer_base[base] = index;
+    _current_sbuffer_index = index;
+
+    report_my_gl_errors();
+  }
+}
+
+/**
+ * Frees the GL resources previously allocated for the data.  This function
+ * should never be called directly; instead, call Data::release() (or simply
+ * let the Data destruct).
+ */
+void CLP(GraphicsStateGuardian)::
+release_shader_buffer(BufferContext *bc) {
+  nassertv(_supports_buffers);
+
+  CLP(BufferContext) *gbc = DCAST(CLP(BufferContext), bc);
+
+  if (GLCAT.is_debug() && gl_debug_buffers) {
+    GLCAT.debug()
+      << "deleting shader buffer " << (int)gbc->_index << "\n";
+  }
+
+  // Make sure the buffer is unbound before we delete it.  Not strictly
+  // necessary according to the OpenGL spec, but it might help out a flaky
+  // driver, and we need to keep our internal state consistent anyway.
+  if (_current_sbuffer_index == gbc->_index) {
+    if (GLCAT.is_spam() && gl_debug_buffers) {
+      GLCAT.spam()
+        << "unbinding shader buffer\n";
+    }
+    _glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+    _current_sbuffer_index = 0;
+  }
+
+  _glDeleteBuffers(1, &gbc->_index);
+  report_my_gl_errors();
+
+  gbc->_index = 0;
+
+  delete gbc;
+}
+#endif
+
 #ifndef OPENGLES
 /**
  * Begins a new occlusion query.  After this call, you may call
@@ -6499,7 +6636,7 @@ do_issue_shade_model() {
 
 #ifndef OPENGLES_1
 /**
- *
+ * Called when the current ShaderAttrib state has changed.
  */
 void CLP(GraphicsStateGuardian)::
 do_issue_shader() {
@@ -6512,21 +6649,30 @@ do_issue_shader() {
     shader = _default_shader;
     nassertv(shader != NULL);
   }
-
 #endif
+
   if (shader) {
-    context = shader->prepare_now(get_prepared_objects(), this);
+    if (_current_shader != shader) {
+      context = shader->prepare_now(get_prepared_objects(), this);
+    } else {
+      context = _current_shader_context;
+    }
   }
+
 #ifndef SUPPORT_FIXED_FUNCTION
   // If it failed, try applying the default shader.
   if (shader != _default_shader && (context == 0 || !context->valid())) {
     shader = _default_shader;
     nassertv(shader != NULL);
-    context = shader->prepare_now(get_prepared_objects(), this);
+    if (_current_shader != shader) {
+      context = shader->prepare_now(get_prepared_objects(), this);
+    } else {
+      context = _current_shader_context;
+    }
   }
 #endif
 
-  if (context == 0 || (context->valid() == false)) {
+  if (context == 0 || !context->valid()) {
     if (_current_shader_context != 0) {
       _current_shader_context->unbind();
       _current_shader = 0;
@@ -6538,12 +6684,16 @@ do_issue_shader() {
       // bind the new one.
       if (_current_shader_context != NULL &&
           _current_shader->get_language() != shader->get_language()) {
+        // If it's a different type of shader, make sure to unbind the old.
         _current_shader_context->unbind();
       }
       context->bind();
       _current_shader = shader;
-      _current_shader_context = context;
     }
+
+    // Bind the shader storage buffers.
+    context->update_shader_buffer_bindings(_current_shader_context);
+    _current_shader_context = context;
   }
 
 #ifndef OPENGLES
@@ -10116,6 +10266,7 @@ set_state_and_transform(const RenderState *target,
   }
 #endif
 
+  // Update all of the state that is bound to the shader program.
   if (_current_shader_context != NULL) {
     _current_shader_context->set_state_and_transform(target, transform, _projection_mat);
   }

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

@@ -344,6 +344,12 @@ public:
                        const GeomPrimitivePipelineReader *reader,
                        bool force);
 
+#ifndef OPENGLES
+  virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data);
+  void apply_shader_buffer(GLuint base, ShaderBuffer *buffer);
+  virtual void release_shader_buffer(BufferContext *bc);
+#endif
+
 #ifndef OPENGLES
   virtual void begin_occlusion_query();
   virtual PT(OcclusionQueryContext) end_occlusion_query();
@@ -685,6 +691,9 @@ protected:
   bool _use_vertex_attrib_binding;
   CPT(GeomVertexFormat) _current_vertex_format;
   const GeomVertexColumn *_vertex_attrib_columns[32];
+
+  GLuint _current_sbuffer_index;
+  pvector<GLuint> _current_sbuffer_base;
 #endif
 
   int _active_texture_stage;
@@ -811,6 +820,7 @@ public:
 
 #ifndef OPENGLES_1
   bool _supports_uniform_buffers;
+  bool _supports_shader_buffers;
   PFNGLBINDBUFFERBASEPROC _glBindBufferBase;
 
   bool _supports_buffer_storage;
@@ -995,6 +1005,9 @@ public:
   PFNGLMAKETEXTUREHANDLENONRESIDENTPROC _glMakeTextureHandleNonResident;
   PFNGLUNIFORMHANDLEUI64PROC _glUniformHandleui64;
   PFNGLUNIFORMHANDLEUI64VPROC _glUniformHandleui64v;
+  PFNGLGETPROGRAMINTERFACEIVPROC _glGetProgramInterfaceiv;
+  PFNGLGETPROGRAMRESOURCENAMEPROC _glGetProgramResourceName;
+  PFNGLGETPROGRAMRESOURCEIVPROC _glGetProgramResourceiv;
 #endif  // !OPENGLES
 
   GLenum _edge_clamp;
@@ -1090,6 +1103,7 @@ private:
 
   friend class CLP(VertexBufferContext);
   friend class CLP(IndexBufferContext);
+  friend class CLP(BufferContext);
   friend class CLP(ShaderContext);
   friend class CLP(CgShaderContext);
   friend class CLP(GraphicsBuffer);

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

@@ -324,6 +324,34 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
     }
   }
 
+#ifndef OPENGLES
+  // Get the used shader storage blocks.
+  if (_glgsg->_supports_shader_buffers) {
+    GLint block_count = 0, block_maxlength = 0;
+
+    _glgsg->_glGetProgramInterfaceiv(_glsl_program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &block_count);
+    _glgsg->_glGetProgramInterfaceiv(_glsl_program, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &block_maxlength);
+
+    block_maxlength = max(64, block_maxlength);
+    char *block_name_cstr = (char *)alloca(block_maxlength);
+
+    for (int i = 0; i < block_count; ++i) {
+      block_name_cstr[0] = 0;
+      _glgsg->_glGetProgramResourceName(_glsl_program, GL_SHADER_STORAGE_BLOCK, i, block_maxlength, NULL, block_name_cstr);
+
+      const GLenum props[] = {GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE};
+      GLint values[2];
+      _glgsg->_glGetProgramResourceiv(_glsl_program, GL_SHADER_STORAGE_BLOCK, i, 2, props, 2, NULL, values);
+
+      StorageBlock block;
+      block._name = InternalName::make(block_name_cstr);
+      block._binding_index = values[0];
+      block._min_size = values[1];
+      _storage_blocks.push_back(block);
+    }
+  }
+#endif
+
   // Bind the program, so that we can call glUniform1i for the textures.
   _glgsg->_glUseProgram(_glsl_program);
 
@@ -2659,6 +2687,31 @@ update_shader_texture_bindings(ShaderContext *prev) {
   _glgsg->report_my_gl_errors();
 }
 
+/**
+ * Updates the shader buffer bindings for this shader.
+ */
+void CLP(ShaderContext)::
+update_shader_buffer_bindings(ShaderContext *prev) {
+#ifndef OPENGLES
+  // Update the shader storage buffer bindings.
+  const ShaderAttrib *attrib = _glgsg->_target_shader;
+
+  for (size_t i = 0; i < _storage_blocks.size(); ++i) {
+    StorageBlock &block = _storage_blocks[i];
+
+    ShaderBuffer *buffer = attrib->get_shader_input_buffer(block._name);
+#ifndef NDEBUG
+    if (buffer->get_data_size_bytes() < block._min_size) {
+      GLCAT.error()
+        << "cannot bind " << *buffer << " to shader because it is too small"
+           " (expected at least " << block._min_size << " bytes)\n";
+    }
+#endif
+    _glgsg->apply_shader_buffer(block._binding_index, buffer);
+  }
+#endif
+}
+
 /**
  * This subroutine prints the infolog for a shader.
  */

+ 12 - 0
panda/src/glstuff/glShaderContext_src.h

@@ -55,6 +55,7 @@ public:
   bool update_shader_vertex_arrays(ShaderContext *prev, bool force);
   void disable_shader_texture_bindings() OVERRIDE;
   void update_shader_texture_bindings(ShaderContext *prev) OVERRIDE;
+  void update_shader_buffer_bindings(ShaderContext *prev) OVERRIDE;
 
   INLINE bool uses_standard_vertex_arrays(void);
   INLINE bool uses_custom_vertex_arrays(void);
@@ -87,6 +88,17 @@ private:
   pmap<GLint, GLuint64> _glsl_uniform_handles;
 #endif
 
+#ifndef OPENGLES
+  struct StorageBlock {
+    CPT(InternalName) _name;
+    GLuint _binding_index;
+    GLint _min_size;
+  };
+  typedef pvector<StorageBlock> StorageBlocks;
+  StorageBlocks _storage_blocks;
+  BitArray _used_storage_bindings;
+#endif
+
   struct ImageInput {
     CPT(InternalName) _name;
     CLP(TextureContext) *_gtc;

+ 1 - 0
panda/src/glstuff/glmisc_src.cxx

@@ -331,6 +331,7 @@ void CLP(init_classes)() {
   CLP(SamplerContext)::init_type();
 #endif
   CLP(VertexBufferContext)::init_type();
+  CLP(BufferContext)::init_type();
   CLP(GraphicsBuffer)::init_type();
 
 #ifndef OPENGLES

+ 1 - 0
panda/src/glstuff/glstuff_src.cxx

@@ -21,6 +21,7 @@
 #include "glSamplerContext_src.cxx"
 #include "glVertexBufferContext_src.cxx"
 #include "glIndexBufferContext_src.cxx"
+#include "glBufferContext_src.cxx"
 #include "glOcclusionQueryContext_src.cxx"
 #include "glTimerQueryContext_src.cxx"
 #include "glLatencyQueryContext_src.cxx"

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

@@ -33,6 +33,7 @@
 #include "glSamplerContext_src.h"
 #include "glVertexBufferContext_src.h"
 #include "glIndexBufferContext_src.h"
+#include "glBufferContext_src.h"
 #include "glOcclusionQueryContext_src.h"
 #include "glTimerQueryContext_src.h"
 #include "glLatencyQueryContext_src.h"

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

@@ -199,6 +199,7 @@ GLXContext glXCreateContextAttribsARB (X11_Display *dpy, GLXFBConfig config, GLX
 
 #ifndef GLX_ARB_get_proc_address
 #define GLX_ARB_get_proc_address 1
+typedef void (*__GLXextFuncPtr)(void);
 typedef __GLXextFuncPtr ( *PFNGLXGETPROCADDRESSARBPROC) (const GLubyte *procName);
 #ifdef GLX_GLXEXT_PROTOTYPES
 __GLXextFuncPtr glXGetProcAddressARB (const GLubyte *procName);

+ 1 - 0
panda/src/gobj/p3gobj_composite2.cxx

@@ -5,6 +5,7 @@
 #include "samplerContext.cxx"
 #include "samplerState.cxx"
 #include "savedContext.cxx"
+#include "shaderBuffer.cxx"
 #include "shaderContext.cxx"
 #include "shader.cxx"
 #include "simpleAllocator.cxx"

+ 5 - 2
panda/src/gobj/preparedGraphicsObjects.I

@@ -45,6 +45,7 @@ release_all() {
   _texture_residency.set_levels();
   _vbuffer_residency.set_levels();
   _ibuffer_residency.set_levels();
+  _sbuffer_residency.set_levels();
 }
 
 /**
@@ -58,7 +59,8 @@ get_num_queued() const {
           get_num_queued_geoms() +
           get_num_queued_shaders() +
           get_num_queued_vertex_buffers() +
-          get_num_queued_index_buffers());
+          get_num_queued_index_buffers() +
+          get_num_queued_shader_buffers());
 }
 
 /**
@@ -72,7 +74,8 @@ get_num_prepared() const {
           get_num_prepared_geoms() +
           get_num_prepared_shaders() +
           get_num_prepared_vertex_buffers() +
-          get_num_prepared_index_buffers());
+          get_num_prepared_index_buffers() +
+          get_num_prepared_shader_buffers());
 }
 
 /**

+ 165 - 0
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -41,6 +41,7 @@ PreparedGraphicsObjects() :
   _texture_residency(_name, "texture"),
   _vbuffer_residency(_name, "vbuffer"),
   _ibuffer_residency(_name, "ibuffer"),
+  _sbuffer_residency(_name, "sbuffer"),
   _graphics_memory_lru("graphics_memory_lru", graphics_memory_limit),
   _sampler_object_lru("sampler_object_lru", sampler_object_limit)
 {
@@ -121,6 +122,16 @@ PreparedGraphicsObjects::
     delete ibc;
   }
   _released_index_buffers.clear();
+
+  release_all_shader_buffers();
+  Buffers::iterator bci;
+  for (bci = _released_shader_buffers.begin();
+       bci != _released_shader_buffers.end();
+       ++bci) {
+    BufferContext *bc = (BufferContext *)(*bci);
+    delete bc;
+  }
+  _released_shader_buffers.clear();
 }
 
 /**
@@ -167,6 +178,9 @@ show_residency_trackers(ostream &out) const {
 
   out << "\nIndex buffers:\n";
   _ibuffer_residency.write(out, 2);
+
+  out << "\nShader buffers:\n";
+  _sbuffer_residency.write(out, 2);
 }
 
 /**
@@ -1195,6 +1209,155 @@ prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) {
   return ibc;
 }
 
+/**
+ * Indicates that a buffer would like to be put on the list to be prepared
+ * when the GSG is next ready to do this (presumably at the next frame).
+ */
+void PreparedGraphicsObjects::
+enqueue_shader_buffer(ShaderBuffer *data) {
+  ReMutexHolder holder(_lock);
+
+  _enqueued_shader_buffers.insert(data);
+}
+
+/**
+ * Returns true if the index buffer has been queued on this GSG, false
+ * otherwise.
+ */
+bool PreparedGraphicsObjects::
+is_shader_buffer_queued(const ShaderBuffer *data) const {
+  ReMutexHolder holder(_lock);
+
+  EnqueuedShaderBuffers::const_iterator qi = _enqueued_shader_buffers.find((ShaderBuffer *)data);
+  return (qi != _enqueued_shader_buffers.end());
+}
+
+/**
+ * Removes a buffer from the queued list of data arrays to be prepared.
+ * Normally it is not necessary to call this, unless you change your mind
+ * about preparing it at the last minute, since the data will automatically be
+ * dequeued and prepared at the next frame.
+ *
+ * The return value is true if the buffer is successfully dequeued, false if
+ * it had not been queued.
+ */
+bool PreparedGraphicsObjects::
+dequeue_shader_buffer(ShaderBuffer *data) {
+  ReMutexHolder holder(_lock);
+
+  EnqueuedShaderBuffers::iterator qi = _enqueued_shader_buffers.find(data);
+  if (qi != _enqueued_shader_buffers.end()) {
+    _enqueued_shader_buffers.erase(qi);
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Returns true if the index buffer has been prepared on this GSG, false
+ * otherwise.
+ */
+bool PreparedGraphicsObjects::
+is_shader_buffer_prepared(const ShaderBuffer *data) const {
+  return data->is_prepared((PreparedGraphicsObjects *)this);
+}
+
+/**
+ * Indicates that a data context, created by a previous call to
+ * prepare_shader_buffer(), is no longer needed.  The driver resources will not
+ * be freed until some GSG calls update(), indicating it is at a stage where
+ * it is ready to release datas--this prevents conflicts from threading or
+ * multiple GSG's sharing datas (we have no way of knowing which graphics
+ * context is currently active, or what state it's in, at the time
+ * release_shader_buffer is called).
+ */
+void PreparedGraphicsObjects::
+release_shader_buffer(BufferContext *bc) {
+  ReMutexHolder holder(_lock);
+
+  bool removed = (_prepared_shader_buffers.erase(bc) != 0);
+  nassertv(removed);
+
+  _released_shader_buffers.insert(bc);
+}
+
+/**
+ * Releases all datas at once.  This will force them to be reloaded into data
+ * memory for all GSG's that share this object.  Returns the number of datas
+ * released.
+ */
+int PreparedGraphicsObjects::
+release_all_shader_buffers() {
+  ReMutexHolder holder(_lock);
+
+  int num_shader_buffers = (int)_prepared_shader_buffers.size() + (int)_enqueued_shader_buffers.size();
+
+  Buffers::iterator bci;
+  for (bci = _prepared_shader_buffers.begin();
+       bci != _prepared_shader_buffers.end();
+       ++bci) {
+
+    BufferContext *bc = (BufferContext *)(*bci);
+    _released_shader_buffers.insert(bc);
+  }
+
+  _prepared_shader_buffers.clear();
+  _enqueued_shader_buffers.clear();
+
+  return num_shader_buffers;
+}
+
+/**
+ * Returns the number of index buffers that have been enqueued to be prepared
+ * on this GSG.
+ */
+int PreparedGraphicsObjects::
+get_num_queued_shader_buffers() const {
+  return _enqueued_shader_buffers.size();
+}
+
+/**
+ * Returns the number of index buffers that have already been prepared on this
+ * GSG.
+ */
+int PreparedGraphicsObjects::
+get_num_prepared_shader_buffers() const {
+  return _prepared_shader_buffers.size();
+}
+
+/**
+ * Immediately creates a new BufferContext for the indicated data and
+ * returns it.  This assumes that the GraphicsStateGuardian is the currently
+ * active rendering context and that it is ready to accept new datas.  If this
+ * is not necessarily the case, you should use enqueue_shader_buffer() instead.
+ *
+ * Normally, this function is not called directly.  Call Data::prepare_now()
+ * instead.
+ *
+ * The BufferContext contains all of the pertinent information needed by
+ * the GSG to keep track of this one particular data, and will exist as long
+ * as the data is ready to be rendered.
+ *
+ * When either the Data or the PreparedGraphicsObjects object destructs, the
+ * BufferContext will be deleted.
+ */
+BufferContext *PreparedGraphicsObjects::
+prepare_shader_buffer_now(ShaderBuffer *data, GraphicsStateGuardianBase *gsg) {
+  ReMutexHolder holder(_lock);
+
+  // Ask the GSG to create a brand new BufferContext.  There might be
+  // several GSG's sharing the same set of datas; if so, it doesn't matter
+  // which of them creates the context (since they're all shared anyway).
+  BufferContext *bc = gsg->prepare_shader_buffer(data);
+
+  if (bc != (BufferContext *)NULL) {
+    bool prepared = _prepared_shader_buffers.insert(bc).second;
+    nassertr(prepared, bc);
+  }
+
+  return bc;
+}
+
 /**
  * This is called by the GraphicsStateGuardian to indicate that it is about to
  * begin processing of the frame.
@@ -1276,6 +1439,7 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   _texture_residency.begin_frame(current_thread);
   _vbuffer_residency.begin_frame(current_thread);
   _ibuffer_residency.begin_frame(current_thread);
+  _sbuffer_residency.begin_frame(current_thread);
 
   // Now prepare all the textures, geoms, and buffers awaiting preparation.
   EnqueuedTextures::iterator qti;
@@ -1359,6 +1523,7 @@ end_frame(Thread *current_thread) {
   _texture_residency.end_frame(current_thread);
   _vbuffer_residency.end_frame(current_thread);
   _ibuffer_residency.end_frame(current_thread);
+  _sbuffer_residency.end_frame(current_thread);
 }
 
 /**

+ 19 - 0
panda/src/gobj/preparedGraphicsObjects.h

@@ -22,6 +22,7 @@
 #include "geomVertexArrayData.h"
 #include "geomPrimitive.h"
 #include "shader.h"
+#include "shaderBuffer.h"
 #include "pointerTo.h"
 #include "pStatCollector.h"
 #include "pset.h"
@@ -35,6 +36,7 @@ class GeomContext;
 class ShaderContext;
 class VertexBufferContext;
 class IndexBufferContext;
+class BufferContext;
 class GraphicsStateGuardianBase;
 
 /**
@@ -142,6 +144,19 @@ PUBLISHED:
   prepare_index_buffer_now(GeomPrimitive *data,
                            GraphicsStateGuardianBase *gsg);
 
+  void enqueue_shader_buffer(ShaderBuffer *data);
+  bool is_shader_buffer_queued(const ShaderBuffer *data) const;
+  bool dequeue_shader_buffer(ShaderBuffer *data);
+  bool is_shader_buffer_prepared(const ShaderBuffer *data) const;
+  void release_shader_buffer(BufferContext *bc);
+  int release_all_shader_buffers();
+  int get_num_queued_shader_buffers() const;
+  int get_num_prepared_shader_buffers() const;
+
+  BufferContext *
+  prepare_shader_buffer_now(ShaderBuffer *data,
+                            GraphicsStateGuardianBase *gsg);
+
 public:
   void begin_frame(GraphicsStateGuardianBase *gsg,
                    Thread *current_thread);
@@ -160,6 +175,7 @@ private:
   typedef phash_set<BufferContext *, pointer_hash> Buffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
   typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;
+  typedef phash_set< PT(ShaderBuffer) > EnqueuedShaderBuffers;
 
   // Sampler states are stored a little bit differently, as they are mapped by
   // value and can't store the list of prepared samplers.
@@ -207,6 +223,8 @@ private:
   EnqueuedVertexBuffers _enqueued_vertex_buffers;
   Buffers _prepared_index_buffers, _released_index_buffers;
   EnqueuedIndexBuffers _enqueued_index_buffers;
+  Buffers _prepared_shader_buffers, _released_shader_buffers;
+  EnqueuedShaderBuffers _enqueued_shader_buffers;
 
   BufferCache _vertex_buffer_cache;
   BufferCacheLRU _vertex_buffer_cache_lru;
@@ -220,6 +238,7 @@ public:
   BufferResidencyTracker _texture_residency;
   BufferResidencyTracker _vbuffer_residency;
   BufferResidencyTracker _ibuffer_residency;
+  BufferResidencyTracker _sbuffer_residency;
 
   AdaptiveLru _graphics_memory_lru;
   SimpleLru _sampler_object_lru;

+ 36 - 3
panda/src/gobj/shader.cxx

@@ -2380,6 +2380,12 @@ do_read_source(string &into, const Filename &fn, BamCacheRecord *record) {
     _last_modified = max(_last_modified, vf->get_timestamp());
     _source_files.push_back(vf->get_filename());
   }
+
+  // Strip trailing whitespace.
+  while (!into.empty() && isspace(into[into.size() - 1])) {
+    into.resize(into.size() - 1);
+  }
+
   return true;
 }
 
@@ -2521,6 +2527,11 @@ r_preprocess_source(ostream &out, const Filename &fn,
       line += line2.substr(block_end + 2);
     }
 
+    // Strip trailing whitespace.
+    while (!line.empty() && isspace(line[line.size() - 1])) {
+      line.resize(line.size() - 1);
+    }
+
     // Check if this line contains a #directive.
     char directive[64];
     if (line.size() < 8 || sscanf(line.c_str(), " # %63s", directive) != 1) {
@@ -2940,6 +2951,14 @@ load(const Filename &file, ShaderLanguage lang) {
   }
 
   _load_table[sfile] = shader;
+
+  if (cache_generated_shaders) {
+    ShaderTable::const_iterator i = _make_table.find(shader->_text);
+    if (i != _make_table.end() && (lang == SL_none || lang == i->second->_language)) {
+      return i->second;
+    }
+    _make_table[shader->_text] = shader;
+  }
   return shader;
 }
 
@@ -2970,6 +2989,14 @@ load(ShaderLanguage lang, const Filename &vertex,
   }
 
   _load_table[sfile] = shader;
+
+  if (cache_generated_shaders) {
+    ShaderTable::const_iterator i = _make_table.find(shader->_text);
+    if (i != _make_table.end() && (lang == SL_none || lang == i->second->_language)) {
+      return i->second;
+    }
+    _make_table[shader->_text] = shader;
+  }
   return shader;
 }
 
@@ -3025,14 +3052,21 @@ load_compute(ShaderLanguage lang, const Filename &fn) {
   if (!shader->read(sfile, record)) {
     return NULL;
   }
+  _load_table[sfile] = shader;
+
+  if (cache_generated_shaders) {
+    ShaderTable::const_iterator i = _make_table.find(shader->_text);
+    if (i != _make_table.end() && (lang == SL_none || lang == i->second->_language)) {
+      return i->second;
+    }
+    _make_table[shader->_text] = shader;
+  }
 
   // It makes little sense to cache the shader before compilation, so we keep
   // the record for when we have the compiled the shader.
   swap(shader->_record, record);
   shader->_cache_compiled_shader = BamCache::get_global_ptr()->get_cache_compiled_shaders();
   shader->_fullpath = shader->_source_files[0];
-
-  _load_table[sfile] = shader;
   return shader;
 }
 
@@ -3166,7 +3200,6 @@ make_compute(ShaderLanguage lang, const string &body) {
   sbody._separate = true;
   sbody._compute = body;
 
-
   if (cache_generated_shaders) {
     ShaderTable::const_iterator i = _make_table.find(sbody);
     if (i != _make_table.end() && (lang == SL_none || lang == i->second->_language)) {

+ 63 - 0
panda/src/gobj/shaderBuffer.I

@@ -0,0 +1,63 @@
+/**
+ * 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 shaderBuffer.I
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+/**
+ * Creates an uninitialized buffer object with the given size.  For now, these
+ * parameters cannot be modified, but this may change in the future.
+ */
+INLINE ShaderBuffer::
+ShaderBuffer(const string &name, uint64_t size, UsageHint usage_hint) :
+  Namable(name),
+  _data_size_bytes(size),
+  _usage_hint(usage_hint) {
+}
+
+/**
+ * Creates a buffer object initialized with the given data.  For now, these
+ * parameters cannot be modified, but this may change in the future.
+ */
+INLINE ShaderBuffer::
+ShaderBuffer(const string &name, pvector<unsigned char> initial_data, UsageHint usage_hint) :
+  Namable(name),
+  _data_size_bytes(initial_data.size()),
+  _usage_hint(usage_hint),
+  _initial_data(initial_data) {
+}
+
+/**
+ * Returns the buffer size in bytes.
+ */
+INLINE uint64_t ShaderBuffer::
+get_data_size_bytes() const {
+  return _data_size_bytes;
+}
+
+/**
+ * Returns the buffer usage hint.
+ */
+INLINE GeomEnums::UsageHint ShaderBuffer::
+get_usage_hint() const {
+  return _usage_hint;
+}
+
+/**
+ * Returns a pointer to the initial buffer data, or NULL if not specified.
+ */
+INLINE const unsigned char *ShaderBuffer::
+get_initial_data() const {
+  if (_initial_data.empty()) {
+    return NULL;
+  } else {
+    return &_initial_data[0];
+  }
+}

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

@@ -0,0 +1,193 @@
+/**
+ * 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 shaderBuffer.cxx
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+#include "shaderBuffer.h"
+#include "preparedGraphicsObjects.h"
+
+TypeHandle ShaderBuffer::_type_handle;
+
+/**
+ *
+ */
+void ShaderBuffer::
+output(ostream &out) const {
+  out << "buffer " << get_name() << ", " << _data_size_bytes << "B, " << _usage_hint;
+}
+
+/**
+ * Indicates that the data should be enqueued to be prepared in the indicated
+ * prepared_objects at the beginning of the next frame.  This will ensure the
+ * data is already loaded into the GSG if it is expected to be rendered soon.
+ *
+ * Use this function instead of prepare_now() to preload datas from a user
+ * interface standpoint.
+ */
+void ShaderBuffer::
+prepare(PreparedGraphicsObjects *prepared_objects) {
+  prepared_objects->enqueue_shader_buffer(this);
+}
+
+/**
+ * Returns true if the data has already been prepared or enqueued for
+ * preparation on the indicated GSG, false otherwise.
+ */
+bool ShaderBuffer::
+is_prepared(PreparedGraphicsObjects *prepared_objects) const {
+  if (_contexts == (Contexts *)NULL) {
+    return false;
+  }
+  Contexts::const_iterator ci;
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
+    return true;
+  }
+  return prepared_objects->is_shader_buffer_queued(this);
+}
+
+/**
+ * Creates a context for the data on the particular GSG, if it does not
+ * already exist.  Returns the new (or old) BufferContext.  This assumes
+ * that the GraphicsStateGuardian is the currently active rendering context
+ * and that it is ready to accept new datas.  If this is not necessarily the
+ * case, you should use prepare() instead.
+ *
+ * Normally, this is not called directly except by the GraphicsStateGuardian;
+ * a data does not need to be explicitly prepared by the user before it may be
+ * rendered.
+ */
+BufferContext *ShaderBuffer::
+prepare_now(PreparedGraphicsObjects *prepared_objects,
+            GraphicsStateGuardianBase *gsg) {
+  if (_contexts == (Contexts *)NULL) {
+    _contexts = new Contexts;
+  }
+  Contexts::const_iterator ci;
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
+    return (*ci).second;
+  }
+
+  BufferContext *vbc = prepared_objects->prepare_shader_buffer_now(this, gsg);
+  if (vbc != (BufferContext *)NULL) {
+    (*_contexts)[prepared_objects] = vbc;
+  }
+  return vbc;
+}
+
+/**
+ * Frees the data context only on the indicated object, if it exists there.
+ * Returns true if it was released, false if it had not been prepared.
+ */
+bool ShaderBuffer::
+release(PreparedGraphicsObjects *prepared_objects) {
+  if (_contexts != (Contexts *)NULL) {
+    Contexts::iterator ci;
+    ci = _contexts->find(prepared_objects);
+    if (ci != _contexts->end()) {
+      BufferContext *vbc = (*ci).second;
+      prepared_objects->release_shader_buffer(vbc);
+      return true;
+    }
+  }
+
+  // Maybe it wasn't prepared yet, but it's about to be.
+  return prepared_objects->dequeue_shader_buffer(this);
+}
+
+/**
+ * Frees the context allocated on all objects for which the data has been
+ * declared.  Returns the number of contexts which have been freed.
+ */
+int ShaderBuffer::
+release_all() {
+  int num_freed = 0;
+
+  if (_contexts != (Contexts *)NULL) {
+    // We have to traverse a copy of the _contexts list, because the
+    // PreparedGraphicsObjects object will call clear_prepared() in response
+    // to each release_shader_buffer(), and we don't want to be modifying the
+    // _contexts list while we're traversing it.
+    Contexts temp = *_contexts;
+    num_freed = (int)_contexts->size();
+
+    Contexts::const_iterator ci;
+    for (ci = temp.begin(); ci != temp.end(); ++ci) {
+      PreparedGraphicsObjects *prepared_objects = (*ci).first;
+      BufferContext *vbc = (*ci).second;
+      prepared_objects->release_shader_buffer(vbc);
+    }
+
+    // Now that we've called release_shader_buffer() on every known context,
+    // the _contexts list should have completely emptied itself.
+    nassertr(_contexts == NULL, num_freed);
+  }
+
+  return num_freed;
+}
+
+/**
+ * Tells the BamReader how to create objects of type ParamValue.
+ */
+void ShaderBuffer::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderBuffer::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_string(get_name());
+  dg.add_uint64(_data_size_bytes);
+  dg.add_uint8(_usage_hint);
+  dg.add_bool(!_initial_data.empty());
+  dg.append_data(_initial_data.data(), _initial_data.size());
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ParamValue is encountered in the Bam file.  It should create the
+ * ParamValue and extract its information from the file.
+ */
+TypedWritable *ShaderBuffer::
+make_from_bam(const FactoryParams &params) {
+  ShaderBuffer *param = new ShaderBuffer;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  param->fillin(scan, manager);
+
+  return param;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new ParamValue.
+ */
+void ShaderBuffer::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  set_name(scan.get_string());
+  _data_size_bytes = scan.get_uint64();
+  _usage_hint = (UsageHint)scan.get_uint8();
+
+  if (scan.get_bool() && _data_size_bytes > 0) {
+    nassertv_always(_data_size_bytes <= scan.get_remaining_size());
+    _initial_data.resize(_data_size_bytes);
+    scan.extract_bytes(&_initial_data[0], _data_size_bytes);
+  } else {
+    _initial_data.clear();
+  }
+}

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

@@ -0,0 +1,97 @@
+/**
+ * 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 shaderBuffer.h
+ * @author rdb
+ * @date 2016-12-12
+ */
+
+#ifndef SHADERBUFFER_H
+#define SHADERBUFFER_H
+
+#include "pandabase.h"
+#include "namable.h"
+#include "geomEnums.h"
+
+class BufferContext;
+class PreparedGraphicsObjects;
+
+/**
+ * This is a generic buffer object that lives in graphics memory.
+ */
+class EXPCL_PANDA_GOBJ ShaderBuffer : public TypedWritableReferenceCount, public Namable, public GeomEnums {
+private:
+  INLINE ShaderBuffer() DEFAULT_CTOR;
+
+PUBLISHED:
+  INLINE ShaderBuffer(const string &name, uint64_t size, UsageHint usage_hint);
+  INLINE ShaderBuffer(const string &name, pvector<unsigned char> initial_data, UsageHint usage_hint);
+
+public:
+  INLINE uint64_t get_data_size_bytes() const;
+  INLINE UsageHint get_usage_hint() const;
+  INLINE const unsigned char *get_initial_data() const;
+
+  virtual void output(ostream &out) const;
+
+PUBLISHED:
+  MAKE_PROPERTY(data_size_bytes, get_data_size_bytes);
+  MAKE_PROPERTY(usage_hint, get_usage_hint);
+
+  void prepare(PreparedGraphicsObjects *prepared_objects);
+  bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
+
+  BufferContext *prepare_now(PreparedGraphicsObjects *prepared_objects,
+                             GraphicsStateGuardianBase *gsg);
+  bool release(PreparedGraphicsObjects *prepared_objects);
+  int release_all();
+
+private:
+  uint64_t _data_size_bytes;
+  UsageHint _usage_hint;
+  pvector<unsigned char> _initial_data;
+
+  typedef pmap<PreparedGraphicsObjects *, BufferContext *> Contexts;
+  Contexts *_contexts;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    Namable::init_type();
+    register_type(_type_handle, "ShaderBuffer",
+                  TypedWritableReferenceCount::get_class_type(),
+                  Namable::get_class_type());
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const ShaderBuffer &m) {
+  m.output(out);
+  return out;
+}
+
+#include "shaderBuffer.I"
+
+#endif

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

@@ -42,6 +42,7 @@ public:
   INLINE virtual bool update_shader_vertex_arrays(ShaderContext *prev, bool force) { return false; };
   INLINE virtual void disable_shader_texture_bindings() {};
   INLINE virtual void update_shader_texture_bindings(ShaderContext *prev) {};
+  INLINE virtual void update_shader_buffer_bindings(ShaderContext *prev) {};
 
   INLINE virtual bool uses_standard_vertex_arrays(void) { return true; };
   INLINE virtual bool uses_custom_vertex_arrays(void) { return false; };

+ 127 - 24
panda/src/gobj/texture.cxx

@@ -3643,13 +3643,13 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       header.pf.four_cc == 0x30315844) {   // 'DX10'
     // A DirectX 10 style texture, which has an additional header.
     func = read_dds_level_generic_uncompressed;
-    unsigned int format = dds.get_uint32();
+    unsigned int dxgi_format = dds.get_uint32();
     unsigned int dimension = dds.get_uint32();
     unsigned int misc_flag = dds.get_uint32();
     unsigned int array_size = dds.get_uint32();
     /*unsigned int alpha_mode = */dds.get_uint32();
 
-    switch (format) {
+    switch (dxgi_format) {
     case 2:    // DXGI_FORMAT_R32G32B32A32_FLOAT
       format = F_rgba32;
       component_type = T_float;
@@ -3665,6 +3665,11 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       component_type = T_unsigned_short;
       func = read_dds_level_abgr16;
       break;
+    case 16:   // DXGI_FORMAT_R32G32_FLOAT
+      format = F_rg32;
+      component_type = T_float;
+      func = read_dds_level_raw;
+      break;
     case 27:   // DXGI_FORMAT_R8G8B8A8_TYPELESS
     case 28:   // DXGI_FORMAT_R8G8B8A8_UNORM
       format = F_rgba8;
@@ -3688,6 +3693,41 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       component_type = T_byte;
       func = read_dds_level_abgr8;
       break;
+    case 34:   // DXGI_FORMAT_R16G16_FLOAT:
+      format = F_rg16;
+      component_type = T_half_float;
+      func = read_dds_level_raw;
+      break;
+    case 35:   // DXGI_FORMAT_R16G16_UNORM:
+      format = F_rg16;
+      component_type = T_unsigned_short;
+      func = read_dds_level_raw;
+      break;
+    case 37:   // DXGI_FORMAT_R16G16_SNORM:
+      format = F_rg16;
+      component_type = T_short;
+      func = read_dds_level_raw;
+      break;
+    case 40:   // DXGI_FORMAT_D32_FLOAT
+      format = F_depth_component32;
+      component_type = T_float;
+      func = read_dds_level_raw;
+      break;
+    case 41:   // DXGI_FORMAT_R32_FLOAT
+      format = F_r32;
+      component_type = T_float;
+      func = read_dds_level_raw;
+      break;
+    case 42:   // DXGI_FORMAT_R32_UINT
+      format = F_r32i;
+      component_type = T_unsigned_int;
+      func = read_dds_level_raw;
+      break;
+    case 43:   // DXGI_FORMAT_R32_SINT
+      format = F_r32i;
+      component_type = T_int;
+      func = read_dds_level_raw;
+      break;
     case 48:   // DXGI_FORMAT_R8G8_TYPELESS
     case 49:   // DXGI_FORMAT_R8G8_UNORM
       format = F_rg;
@@ -3703,6 +3743,36 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       format = F_rg8i;
       component_type = T_byte;
       break;
+    case 54:   // DXGI_FORMAT_R16_FLOAT:
+      format = F_r16;
+      component_type = T_half_float;
+      func = read_dds_level_raw;
+      break;
+    case 55:   // DXGI_FORMAT_D16_UNORM:
+      format = F_depth_component16;
+      component_type = T_unsigned_short;
+      func = read_dds_level_raw;
+      break;
+    case 56:   // DXGI_FORMAT_R16_UNORM:
+      format = F_r16;
+      component_type = T_unsigned_short;
+      func = read_dds_level_raw;
+      break;
+    case 57:   // DXGI_FORMAT_R16_UINT:
+      format = F_r16i;
+      component_type = T_unsigned_short;
+      func = read_dds_level_raw;
+      break;
+    case 58:   // DXGI_FORMAT_R16_SNORM:
+      format = F_r16;
+      component_type = T_short;
+      func = read_dds_level_raw;
+      break;
+    case 59:   // DXGI_FORMAT_R16_SINT:
+      format = F_r16i;
+      component_type = T_short;
+      func = read_dds_level_raw;
+      break;
     case 60:   // DXGI_FORMAT_R8_TYPELESS
     case 61:   // DXGI_FORMAT_R8_UNORM
       format = F_red;
@@ -3760,7 +3830,6 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       compression = CM_rgtc;
       func = read_dds_level_bc4;
       break;
-      break;
     case 82:   // DXGI_FORMAT_BC5_TYPELESS
     case 83:   // DXGI_FORMAT_BC5_UNORM
       format = F_rg;
@@ -3786,7 +3855,7 @@ do_read_dds(CData *cdata, istream &in, const string &filename, bool header_only)
       break;
     default:
       gobj_cat.error()
-        << filename << ": unsupported DXGI format " << format << ".\n";
+        << filename << ": unsupported DXGI format " << dxgi_format << ".\n";
       return false;
     }
 
@@ -7729,10 +7798,11 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
     // Most common case: one byte per pixel, and the source image shows a
     // maxval of 255.  No scaling is necessary.  Because this is such a common
     // case, we break it out per component for best performance.
+    const xel *array = pnmimage.get_array();
     switch (num_components) {
     case 1:
       for (int j = y_size-1; j >= 0; j--) {
-        xel *row = pnmimage.row(j);
+        const xel *row = array + j * x_size;
         for (int i = 0; i < x_size; i++) {
           *p++ = (uchar)PPM_GETB(row[i]);
         }
@@ -7742,9 +7812,10 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
 
     case 2:
       if (img_has_alpha) {
+        const xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
-          xelval *alpha_row = pnmimage.alpha_row(j);
+          const xel *row = array + j * x_size;
+          const xelval *alpha_row = alpha + j * x_size;
           for (int i = 0; i < x_size; i++) {
             *p++ = (uchar)PPM_GETB(row[i]);
             *p++ = (uchar)alpha_row[i];
@@ -7753,7 +7824,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
         }
       } else {
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
+          const xel *row = array + j * x_size;
           for (int i = 0; i < x_size; i++) {
             *p++ = (uchar)PPM_GETB(row[i]);
             *p++ = (uchar)255;
@@ -7765,7 +7836,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
 
     case 3:
       for (int j = y_size-1; j >= 0; j--) {
-        xel *row = pnmimage.row(j);
+        const xel *row = array + j * x_size;
         for (int i = 0; i < x_size; i++) {
           *p++ = (uchar)PPM_GETB(row[i]);
           *p++ = (uchar)PPM_GETG(row[i]);
@@ -7777,9 +7848,10 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
 
     case 4:
       if (img_has_alpha) {
+        const xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
-          xelval *alpha_row = pnmimage.alpha_row(j);
+          const xel *row = array + j * x_size;
+          const xelval *alpha_row = alpha + j * x_size;
           for (int i = 0; i < x_size; i++) {
             *p++ = (uchar)PPM_GETB(row[i]);
             *p++ = (uchar)PPM_GETG(row[i]);
@@ -7790,7 +7862,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
         }
       } else {
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
+          const xel *row = array + j * x_size;
           for (int i = 0; i < x_size; i++) {
             *p++ = (uchar)PPM_GETB(row[i]);
             *p++ = (uchar)PPM_GETG(row[i]);
@@ -7813,7 +7885,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size,
     for (int j = y_size-1; j >= 0; j--) {
       for (int i = 0; i < x_size; i++) {
         if (is_grayscale) {
-           store_unscaled_short(p, pnmimage.get_gray_val(i, j));
+          store_unscaled_short(p, pnmimage.get_gray_val(i, j));
         } else {
           store_unscaled_short(p, pnmimage.get_blue_val(i, j));
           store_unscaled_short(p, pnmimage.get_green_val(i, j));
@@ -7979,11 +8051,13 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
   const unsigned char *p = &image[idx];
 
   if (component_width == 1) {
+    xel *array = pnmimage.get_array();
     if (is_grayscale) {
       if (has_alpha) {
+        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
-          xelval *alpha_row = pnmimage.alpha_row(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++);
             alpha_row[i] = *p++;
@@ -7991,7 +8065,7 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
         }
       } else {
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
+          xel *row = array + j * x_size;
           for (int i = 0; i < x_size; i++) {
             PPM_PUTB(row[i], *p++);
           }
@@ -7999,9 +8073,10 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
       }
     } else {
       if (has_alpha) {
+        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
-          xelval *alpha_row = pnmimage.alpha_row(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++);
             PPM_PUTG(row[i], *p++);
@@ -8011,7 +8086,7 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
         }
       } else {
         for (int j = y_size-1; j >= 0; j--) {
-          xel *row = pnmimage.row(j);
+          xel *row = array + j * x_size;
           for (int i = 0; i < x_size; i++) {
             PPM_PUTB(row[i], *p++);
             PPM_PUTG(row[i], *p++);
@@ -8258,6 +8333,7 @@ read_dds_level_abgr32(Texture *tex, CData *cdata, const DDSHeader &header, int n
 
   size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
   size_t row_bytes = x_size * 16;
+  nassertr(row_bytes * y_size == size, PTA_uchar());
   PTA_uchar image = PTA_uchar::empty_array(size);
   for (int y = y_size - 1; y >= 0; --y) {
     unsigned char *p = image.p() + y * row_bytes;
@@ -8274,6 +8350,26 @@ read_dds_level_abgr32(Texture *tex, CData *cdata, const DDSHeader &header, int n
   return image;
 }
 
+/**
+ * Called by read_dds for a DDS file that needs no transformations applied.
+ */
+PTA_uchar Texture::
+read_dds_level_raw(Texture *tex, CData *cdata, const DDSHeader &header, int n, istream &in) {
+  int x_size = tex->do_get_expected_mipmap_x_size(cdata, n);
+  int y_size = tex->do_get_expected_mipmap_y_size(cdata, n);
+
+  size_t size = tex->do_get_expected_ram_mipmap_page_size(cdata, n);
+  size_t row_bytes = x_size * cdata->_num_components * cdata->_component_width;
+  nassertr(row_bytes * y_size == size, PTA_uchar());
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  for (int y = y_size - 1; y >= 0; --y) {
+    unsigned char *p = image.p() + y * row_bytes;
+    in.read((char *)p, row_bytes);
+  }
+
+  return image;
+}
+
 /**
  * Called by read_dds for a DDS file whose format isn't one we've specifically
  * optimized.
@@ -8932,13 +9028,20 @@ compare_images(const PNMImage &a, const PNMImage &b) {
   nassertr(a.get_x_size() == b.get_x_size() &&
            a.get_y_size() == b.get_y_size(), false);
 
+  const xel *a_array = a.get_array();
+  const xel *b_array = b.get_array();
+  const xelval *a_alpha = a.get_alpha_array();
+  const xelval *b_alpha = b.get_alpha_array();
+
+  int x_size = a.get_x_size();
+
   int delta = 0;
   for (int yi = 0; yi < a.get_y_size(); ++yi) {
-    xel *a_row = a.row(yi);
-    xel *b_row = b.row(yi);
-    xelval *a_alpha_row = a.alpha_row(yi);
-    xelval *b_alpha_row = b.alpha_row(yi);
-    for (int xi = 0; xi < a.get_x_size(); ++xi) {
+    const xel *a_row = a_array + yi * x_size;
+    const xel *b_row = b_array + yi * x_size;
+    const xelval *a_alpha_row = a_alpha + yi * x_size;
+    const xelval *b_alpha_row = b_alpha + yi * x_size;
+    for (int xi = 0; xi < x_size; ++xi) {
       delta += abs(PPM_GETR(a_row[xi]) - PPM_GETR(b_row[xi]));
       delta += abs(PPM_GETG(a_row[xi]) - PPM_GETG(b_row[xi]));
       delta += abs(PPM_GETB(a_row[xi]) - PPM_GETB(b_row[xi]));

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

@@ -817,6 +817,8 @@ private:
                                          int n, istream &in);
   static PTA_uchar read_dds_level_abgr32(Texture *tex, CData *cdata, const DDSHeader &header,
                                          int n, istream &in);
+  static PTA_uchar read_dds_level_raw(Texture *tex, CData *cdata, const DDSHeader &header,
+                                      int n, istream &in);
   static PTA_uchar read_dds_level_generic_uncompressed(Texture *tex, CData *cdata,
                                                        const DDSHeader &header,
                                                        int n, istream &in);

+ 1 - 1
panda/src/grutil/config_grutil.cxx

@@ -131,6 +131,6 @@ init_libgrutil() {
   MovieTexture::register_with_read_factory();
 
   TexturePool *ts = TexturePool::get_global_ptr();
-  ts->register_texture_type(MovieTexture::make_texture, "avi m2v mov mpg mpeg mp4 wmv asf flv nut ogm mkv ogv");
+  ts->register_texture_type(MovieTexture::make_texture, "avi m2v mov mpg mpeg mp4 wmv asf flv nut ogm mkv ogv webm");
 #endif
 }

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

@@ -31,6 +31,7 @@ class GraphicsOutputBase;
 
 class VertexBufferContext;
 class IndexBufferContext;
+class BufferContext;
 class GeomContext;
 class GeomNode;
 class Geom;
@@ -57,6 +58,7 @@ class SamplerContext;
 class SamplerState;
 class Shader;
 class ShaderContext;
+class ShaderBuffer;
 class RenderState;
 class TransformState;
 class Material;
@@ -162,6 +164,9 @@ public:
   virtual IndexBufferContext *prepare_index_buffer(GeomPrimitive *data)=0;
   virtual void release_index_buffer(IndexBufferContext *ibc)=0;
 
+  virtual BufferContext *prepare_shader_buffer(ShaderBuffer *data)=0;
+  virtual void release_shader_buffer(BufferContext *ibc)=0;
+
   virtual void dispatch_compute(int size_x, int size_y, int size_z)=0;
 
   virtual PT(GeomMunger) get_geom_munger(const RenderState *state,

+ 6 - 2
panda/src/movies/dr_flac.h

@@ -534,7 +534,9 @@ static DRFLAC_INLINE uint64_t drflac__swap_endian_uint64(uint64_t n)
 
 static DRFLAC_INLINE uint32_t drflac__be2host_32(uint32_t n)
 {
-#ifdef __linux__
+#if defined(__BYTE_ORDER__) && (__BYTE_ORDER == __ORDER_LITTLE_ENDIAN__)
+    return drflac__swap_endian_uint32(n);
+#elif defined(__linux__)
     return be32toh(n);
 #else
     if (drflac__is_little_endian()) {
@@ -547,7 +549,9 @@ static DRFLAC_INLINE uint32_t drflac__be2host_32(uint32_t n)
 
 static DRFLAC_INLINE uint64_t drflac__be2host_64(uint64_t n)
 {
-#ifdef __linux__
+#if defined(__BYTE_ORDER__) && (__BYTE_ORDER == __ORDER_LITTLE_ENDIAN__)
+    return drflac__swap_endian_uint64(n);
+#elif defined(__linux__)
     return be64toh(n);
 #else
     if (drflac__is_little_endian()) {

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

@@ -90,6 +90,7 @@ protected:
     CP_point_priority,
     CP_directional_priority,
     CP_spot_priority,
+    CP_area_priority,
   };
 
 private:

+ 8 - 0
panda/src/pgraph/nodePath.I

@@ -1253,6 +1253,14 @@ set_shader_input(CPT_InternalName id, Texture *tex, bool read, bool write, int z
   set_shader_input(new ShaderInput(id, tex, read, write, z, n, priority));
 }
 
+/**
+ *
+ */
+INLINE void NodePath::
+set_shader_input(CPT_InternalName id, ShaderBuffer *buf, int priority) {
+  set_shader_input(new ShaderInput(id, buf, priority));
+}
+
 /**
  *
  */

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

@@ -61,6 +61,7 @@ class GlobPattern;
 class PreparedGraphicsObjects;
 class SamplerState;
 class Shader;
+class ShaderBuffer;
 class ShaderInput;
 
 //
@@ -632,6 +633,7 @@ PUBLISHED:
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, const SamplerState &sampler, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
+  INLINE void set_shader_input(CPT_InternalName id, ShaderBuffer *buf, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const NodePath &np, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const PTA_float &v, int priority=0);
   INLINE void set_shader_input(CPT_InternalName id, const PTA_double &v, int priority=0);

+ 32 - 2
panda/src/pgraph/shaderAttrib.cxx

@@ -25,6 +25,7 @@
 #include "datagram.h"
 #include "datagramIterator.h"
 #include "nodePath.h"
+#include "shaderBuffer.h"
 
 TypeHandle ShaderAttrib::_type_handle;
 int ShaderAttrib::_attrib_slot;
@@ -429,7 +430,8 @@ get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const {
       nassertr(!np.is_empty(), LMatrix4::ident_mat());
       return np.get_transform()->get_mat();
 
-    } else if (p->get_value_type() == ShaderInput::M_numeric && p->get_ptr()._size == 16) {
+    } else if (p->get_value_type() == ShaderInput::M_numeric &&
+               p->get_ptr()._size >= 16 && (p->get_ptr()._size & 15) == 0) {
       const Shader::ShaderPtrData &ptr = p->get_ptr();
 
       switch (ptr._type) {
@@ -455,12 +457,40 @@ get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const {
     }
 
     ostringstream strm;
-    strm << "Shader input " << id->get_name() << " is not a NodePath or LMatrix4.\n";
+    strm << "Shader input " << id->get_name() << " is not a NodePath, LMatrix4 or PTA_LMatrix4.\n";
     nassert_raise(strm.str());
     return LMatrix4::ident_mat();
   }
 }
 
+/**
+ * Returns the ShaderInput as a ShaderBuffer.  Assertion fails if there is
+ * none, or if it is not a ShaderBuffer.
+ */
+ShaderBuffer *ShaderAttrib::
+get_shader_input_buffer(const InternalName *id) const {
+  Inputs::const_iterator i = _inputs.find(id);
+  if (i == _inputs.end()) {
+    ostringstream strm;
+    strm << "Shader input " << id->get_name() << " is not present.\n";
+    nassert_raise(strm.str());
+    return NULL;
+  } else {
+    const ShaderInput *p = (*i).second;
+
+    if (p->get_value_type() == ShaderInput::M_buffer) {
+      ShaderBuffer *value;
+      DCAST_INTO_R(value, p->_value, NULL);
+      return value;
+    }
+
+    ostringstream strm;
+    strm << "Shader input " << id->get_name() << " is not a ShaderBuffer.\n";
+    nassert_raise(strm.str());
+    return NULL;
+  }
+}
+
 /**
  * Returns the shader object associated with the node.  If get_override
  * returns true, but get_shader returns NULL, that means that this attribute

+ 1 - 0
panda/src/pgraph/shaderAttrib.h

@@ -111,6 +111,7 @@ PUBLISHED:
   Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=NULL) const;
   const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const;
   const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const;
+  ShaderBuffer *get_shader_input_buffer(const InternalName *id) const;
 
   static void register_with_read_factory();
 

+ 12 - 0
panda/src/pgraph/shaderInput.I

@@ -55,6 +55,18 @@ ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority) :
 {
 }
 
+/**
+ *
+ */
+INLINE ShaderInput::
+ShaderInput(CPT_InternalName name, ShaderBuffer *buf, int priority) :
+  _name(MOVE(name)),
+  _type(M_buffer),
+  _priority(priority),
+  _value(buf)
+{
+}
+
 /**
  *
  */

+ 6 - 1
panda/src/pgraph/shaderInput.h

@@ -31,6 +31,7 @@
 #include "samplerState.h"
 #include "shader.h"
 #include "texture.h"
+#include "shaderBuffer.h"
 
 /**
  * This is a small container class that can hold any one of the value types
@@ -52,6 +53,7 @@ PUBLISHED:
   INLINE ShaderInput(CPT_InternalName name, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority=0);
+  INLINE ShaderInput(CPT_InternalName name, ShaderBuffer *buf, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const PTA_float &ptr, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const PTA_LVecBase4f &ptr, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const PTA_LVecBase3f &ptr, int priority=0);
@@ -96,7 +98,8 @@ PUBLISHED:
     M_numeric,
     M_texture_sampler,
     M_param,
-    M_texture_image
+    M_texture_image,
+    M_buffer,
   };
 
   INLINE const InternalName *get_name() const;
@@ -123,6 +126,8 @@ private:
   int _priority;
   int _type;
 
+  friend class ShaderAttrib;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 3 - 0
panda/src/pgraphnodes/config_pgraphnodes.cxx

@@ -26,6 +26,7 @@
 #include "lodNode.h"
 #include "nodeCullCallbackData.h"
 #include "pointLight.h"
+#include "rectangleLight.h"
 #include "selectiveChildNode.h"
 #include "sequenceNode.h"
 #include "shaderGenerator.h"
@@ -121,6 +122,7 @@ init_libpgraphnodes() {
   LODNode::init_type();
   NodeCullCallbackData::init_type();
   PointLight::init_type();
+  RectangleLight::init_type();
   SelectiveChildNode::init_type();
   SequenceNode::init_type();
   ShaderGenerator::init_type();
@@ -137,6 +139,7 @@ init_libpgraphnodes() {
   LightNode::register_with_read_factory();
   LODNode::register_with_read_factory();
   PointLight::register_with_read_factory();
+  RectangleLight::register_with_read_factory();
   SelectiveChildNode::register_with_read_factory();
   SequenceNode::register_with_read_factory();
   SphereLight::register_with_read_factory();

+ 1 - 0
panda/src/pgraphnodes/p3pgraphnodes_composite2.cxx

@@ -1,5 +1,6 @@
 #include "nodeCullCallbackData.cxx"
 #include "pointLight.cxx"
+#include "rectangleLight.cxx"
 #include "sceneGraphAnalyzer.cxx"
 #include "selectiveChildNode.cxx"
 #include "sequenceNode.cxx"

+ 59 - 0
panda/src/pgraphnodes/rectangleLight.I

@@ -0,0 +1,59 @@
+/**
+ * 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 rectangleLight.I
+ * @author rdb
+ * @date 2016-12-19
+ */
+
+/**
+ *
+ */
+INLINE RectangleLight::CData::
+CData() :
+  _max_distance(make_inf((PN_stdfloat)0))
+{
+}
+
+/**
+ *
+ */
+INLINE RectangleLight::CData::
+CData(const RectangleLight::CData &copy) :
+  _max_distance(copy._max_distance)
+{
+}
+
+/**
+ * Returns the color of specular highlights generated by the light.  This is
+ * usually the same as get_color().
+ */
+INLINE const LColor &RectangleLight::
+get_specular_color() const {
+  return get_color();
+}
+
+/**
+ * Returns the maximum distance at which the light has any effect, as previously
+ * specified by set_max_distance.
+ */
+INLINE PN_stdfloat RectangleLight::
+get_max_distance() const {
+  CDReader cdata(_cycler);
+  return cdata->_max_distance;
+}
+
+/**
+ * Sets the radius of the light's sphere of influence.  Beyond this distance, the
+ * light may be attenuated to zero, if this is supported by the shader.
+ */
+INLINE void RectangleLight::
+set_max_distance(PN_stdfloat max_distance) {
+  CDWriter cdata(_cycler);
+  cdata->_max_distance = max_distance;
+}

+ 150 - 0
panda/src/pgraphnodes/rectangleLight.cxx

@@ -0,0 +1,150 @@
+/**
+ * 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 rectangleLight.cxx
+ * @author rdb
+ * @date 2016-12-19
+ */
+
+#include "rectangleLight.h"
+#include "graphicsStateGuardianBase.h"
+#include "bamWriter.h"
+#include "bamReader.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+
+TypeHandle RectangleLight::_type_handle;
+
+/**
+ *
+ */
+CycleData *RectangleLight::CData::
+make_copy() const {
+  return new CData(*this);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void RectangleLight::CData::
+write_datagram(BamWriter *manager, Datagram &dg) const {
+  dg.add_stdfloat(_max_distance);
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new Light.
+ */
+void RectangleLight::CData::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _max_distance = scan.get_stdfloat();
+}
+
+/**
+ *
+ */
+RectangleLight::
+RectangleLight(const string &name) :
+  LightLensNode(name)
+{
+}
+
+/**
+ * Do not call the copy constructor directly; instead, use make_copy() or
+ * copy_subgraph() to make a copy of a node.
+ */
+RectangleLight::
+RectangleLight(const RectangleLight &copy) :
+  LightLensNode(copy),
+  _cycler(copy._cycler)
+{
+}
+
+/**
+ * Returns a newly-allocated PandaNode that is a shallow copy of this one.  It
+ * will be a different pointer, but its internal data may or may not be shared
+ * with that of the original PandaNode.  No children will be copied.
+ */
+PandaNode *RectangleLight::
+make_copy() const {
+  return new RectangleLight(*this);
+}
+
+/**
+ *
+ */
+void RectangleLight::
+write(ostream &out, int indent_level) const {
+  LightLensNode::write(out, indent_level);
+  indent(out, indent_level) << *this << "\n";
+}
+
+/**
+ * Returns the relative priority associated with all lights of this class.
+ * This priority is used to order lights whose instance priority
+ * (get_priority()) is the same--the idea is that other things being equal,
+ * AmbientLights (for instance) are less important than DirectionalLights.
+ */
+int RectangleLight::
+get_class_priority() const {
+  return (int)CP_area_priority;
+}
+
+/**
+ *
+ */
+void RectangleLight::
+bind(GraphicsStateGuardianBase *gsg, const NodePath &light, int light_id) {
+}
+
+/**
+ * Tells the BamReader how to create objects of type RectangleLight.
+ */
+void RectangleLight::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void RectangleLight::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  LightLensNode::write_datagram(manager, dg);
+  manager->write_cdata(dg, _cycler);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type RectangleLight is encountered in the Bam file.  It should create the
+ * RectangleLight and extract its information from the file.
+ */
+TypedWritable *RectangleLight::
+make_from_bam(const FactoryParams &params) {
+  RectangleLight *node = new RectangleLight("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new RectangleLight.
+ */
+void RectangleLight::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  LightLensNode::fillin(scan, manager);
+
+  manager->read_cdata(scan, _cycler);
+}

+ 98 - 0
panda/src/pgraphnodes/rectangleLight.h

@@ -0,0 +1,98 @@
+/**
+ * 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 rectangleLight.h
+ * @author rdb
+ * @date 2016-12-19
+ */
+
+#ifndef RECTANGLELIGHT_H
+#define RECTANGLELIGHT_H
+
+#include "pandabase.h"
+
+#include "lightLensNode.h"
+#include "pointLight.h"
+
+/**
+ * This is a type of area light that is an axis aligned rectangle, pointing
+ * along the Y axis in the positive direction.
+ */
+class EXPCL_PANDA_PGRAPHNODES RectangleLight : public LightLensNode {
+PUBLISHED:
+  RectangleLight(const string &name);
+
+protected:
+  RectangleLight(const RectangleLight &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+  virtual void write(ostream &out, int indent_level) const;
+
+PUBLISHED:
+  INLINE const LColor &get_specular_color() const FINAL;
+
+  INLINE PN_stdfloat get_max_distance() const;
+  INLINE void set_max_distance(PN_stdfloat max_distance);
+  MAKE_PROPERTY(max_distance, get_max_distance, set_max_distance);
+
+  virtual int get_class_priority() const;
+
+public:
+  virtual void bind(GraphicsStateGuardianBase *gsg, const NodePath &light,
+                    int light_id);
+
+private:
+  // This is the data that must be cycled between pipeline stages.
+  class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
+  public:
+    INLINE CData();
+    INLINE CData(const CData &copy);
+    virtual CycleData *make_copy() const;
+    virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
+    virtual void fillin(DatagramIterator &scan, BamReader *manager);
+    virtual TypeHandle get_parent_type() const {
+      return RectangleLight::get_class_type();
+    }
+
+    PN_stdfloat _max_distance;
+  };
+
+  PipelineCycler<CData> _cycler;
+  typedef CycleDataReader<CData> CDReader;
+  typedef CycleDataWriter<CData> CDWriter;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    LightLensNode::init_type();
+    register_type(_type_handle, "RectangleLight",
+                  LightLensNode::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 "rectangleLight.I"
+
+#endif

+ 18 - 0
panda/src/pipeline/pythonThread.cxx

@@ -37,6 +37,13 @@ PythonThread(PyObject *function, PyObject *args,
   }
 
   set_args(args);
+
+#ifndef SIMPLE_THREADS
+  // Ensure that the Python threading system is initialized and ready to go.
+#ifdef WITH_THREAD  // This symbol defined within Python.h
+  PyEval_InitThreads();
+#endif
+#endif
 }
 
 /**
@@ -44,9 +51,20 @@ PythonThread(PyObject *function, PyObject *args,
  */
 PythonThread::
 ~PythonThread() {
+  // Unfortunately, we need to grab the GIL to release these things,
+  // since the destructor could be called from any thread.
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyGILState_STATE gstate;
+  gstate = PyGILState_Ensure();
+#endif
+
   Py_DECREF(_function);
   Py_XDECREF(_args);
   Py_XDECREF(_result);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyGILState_Release(gstate);
+#endif
 }
 
 /**

+ 2 - 2
panda/src/pnmimagetypes/pnmFileTypeTIFF.cxx

@@ -1046,13 +1046,13 @@ write_data(xel *array, xelval *alpha) {
       bytesperrow = _x_size * samplesperpixel;
     } else if ( grayscale ) {
       samplesperpixel = 1;
-      bitspersample = pm_maxvaltobits( _maxval );
+      bitspersample = min(8, pm_maxvaltobits(_maxval));
       photometric = PHOTOMETRIC_MINISBLACK;
       i = 8 / bitspersample;
       bytesperrow = ( _x_size + i - 1 ) / i;
     } else {
       samplesperpixel = 1;
-      bitspersample = 8;
+      bitspersample = min(8, pm_maxvaltobits(_maxval));
       photometric = PHOTOMETRIC_PALETTE;
       bytesperrow = _x_size;
     }

+ 3 - 3
panda/src/pstatclient/pStatProperties.cxx

@@ -151,9 +151,9 @@ static TimeCollectorProperties time_properties[] = {
 
 static LevelCollectorProperties level_properties[] = {
   { 1, "Graphics memory",                  { 0.0, 0.0, 1.0 },  "MB", 64, 1048576 },
-  { 1, "Vertex buffer switch",             { 0.0, 0.6, 0.8 },  "", 500 },
-  { 1, "Vertex buffer switch:Vertex",      { 0.8, 0.0, 0.6 } },
-  { 1, "Vertex buffer switch:Index",       { 0.8, 0.6, 0.3 } },
+  { 1, "Buffer switch",                    { 0.0, 0.6, 0.8 },  "", 500 },
+  { 1, "Buffer switch:Vertex",             { 0.8, 0.0, 0.6 } },
+  { 1, "Buffer switch:Index",              { 0.8, 0.6, 0.3 } },
   { 1, "Geom cache size",                  { 0.6, 0.8, 0.6 },  "", 500 },
   { 1, "Geom cache size:Active",           { 0.9, 1.0, 0.3 },  "", 500 },
   { 1, "Geom cache operations",            { 1.0, 0.6, 0.6 },  "", 500 },

+ 16 - 3
panda/src/putil/cachedTypedWritableReferenceCount.I

@@ -126,7 +126,7 @@ cache_ref() const {
 #endif
 
   ref();
-  AtomicAdjust::inc(((CachedTypedWritableReferenceCount *)this)->_cache_ref_count);
+  AtomicAdjust::inc(_cache_ref_count);
 }
 
 /**
@@ -147,7 +147,7 @@ cache_unref() const {
   // you can't use PointerTo's?
   nassertr(_cache_ref_count > 0, 0);
 
-  AtomicAdjust::dec(((CachedTypedWritableReferenceCount *)this)->_cache_ref_count);
+  AtomicAdjust::dec(_cache_ref_count);
   return ReferenceCount::unref();
 }
 
@@ -164,6 +164,19 @@ test_ref_count_integrity() const {
 #endif
 }
 
+/**
+ * Decrements the cache reference count without affecting the normal reference
+ * count.  Don't use this.
+ */
+INLINE void CachedTypedWritableReferenceCount::
+cache_ref_only() const {
+#ifdef _DEBUG
+  nassertv(test_ref_count_integrity());
+#endif
+
+  AtomicAdjust::inc(_cache_ref_count);
+}
+
 /**
  * Decrements the cache reference count without affecting the normal reference
  * count.  Intended to be called by derived classes only, presumably to
@@ -180,7 +193,7 @@ cache_unref_only() const {
   // you can't use PointerTo's?
   nassertv(_cache_ref_count > 0);
 
-  AtomicAdjust::dec(((CachedTypedWritableReferenceCount *)this)->_cache_ref_count);
+  AtomicAdjust::dec(_cache_ref_count);
 }
 
 /**

+ 4 - 1
panda/src/putil/cachedTypedWritableReferenceCount.h

@@ -46,12 +46,15 @@ PUBLISHED:
 
   MAKE_PROPERTY(cache_ref_count, get_cache_ref_count);
 
+public:
+  INLINE void cache_ref_only() const;
+
 protected:
   INLINE void cache_unref_only() const;
   bool do_test_ref_count_integrity() const;
 
 private:
-  AtomicAdjust::Integer _cache_ref_count;
+  mutable AtomicAdjust::Integer _cache_ref_count;
 
 public:
   static TypeHandle get_class_type() {

+ 17 - 0
panda/src/putil/copyOnWriteObject.cxx

@@ -35,4 +35,21 @@ unref() const {
   }
   return is_zero;
 }
+
+/**
+ * Explicitly increments the cache reference count only.  Don't use this.
+ *
+ * In the case of a CopyOnWriteObject, when the reference count decrements
+ * down to the cache reference count, the object is implicitly unlocked.
+ */
+void CopyOnWriteObject::
+cache_ref_only() const {
+  MutexHolder holder(_lock_mutex);
+  CachedTypedWritableReferenceCount::cache_ref_only();
+  if (get_ref_count() == get_cache_ref_count()) {
+    ((CopyOnWriteObject *)this)->_lock_status = LS_unlocked;
+    ((CopyOnWriteObject *)this)->_locking_thread = NULL;
+    ((CopyOnWriteObject *)this)->_lock_cvar.notify();
+  }
+}
 #endif  // COW_THREADED

+ 3 - 0
panda/src/putil/copyOnWriteObject.h

@@ -49,6 +49,9 @@ PUBLISHED:
   virtual bool unref() const;
   INLINE void cache_ref() const;
   INLINE bool cache_unref() const;
+
+public:
+  void cache_ref_only() const;
 #endif  // COW_THREADED
 
 protected:

+ 86 - 11
panda/src/putil/copyOnWritePointer.I

@@ -74,23 +74,58 @@ INLINE CopyOnWritePointer::
  *
  */
 INLINE CopyOnWritePointer::
-CopyOnWritePointer(CopyOnWritePointer &&move) NOEXCEPT :
-  _cow_object(move._cow_object)
+CopyOnWritePointer(CopyOnWritePointer &&from) NOEXCEPT :
+  _cow_object(from._cow_object)
 {
   // Steal the other's reference count.
-  move._cow_object = (CopyOnWriteObject *)NULL;
+  from._cow_object = (CopyOnWriteObject *)NULL;
+}
+
+/**
+ *
+ */
+INLINE CopyOnWritePointer::
+CopyOnWritePointer(PointerTo<CopyOnWriteObject> &&from) NOEXCEPT :
+  _cow_object(from.p())
+{
+  // Steal the other's reference count, but because it is a regular pointer,
+  // we do need to include the cache reference count.
+  if (_cow_object != (CopyOnWriteObject *)NULL) {
+    _cow_object->cache_ref_only();
+  }
+  from.cheat() = NULL;
 }
 
 /**
  *
  */
 INLINE void CopyOnWritePointer::
-operator = (CopyOnWritePointer &&move) NOEXCEPT {
+operator = (CopyOnWritePointer &&from) NOEXCEPT {
   // Protect against self-move-assignment.
-  if (move._cow_object != _cow_object) {
+  if (from._cow_object != _cow_object) {
     CopyOnWriteObject *old_object = _cow_object;
-    _cow_object = move._cow_object;
-    move._cow_object = NULL;
+    _cow_object = from._cow_object;
+    from._cow_object = NULL;
+
+    if (old_object != (CopyOnWriteObject *)NULL) {
+      cache_unref_delete(old_object);
+    }
+  }
+}
+
+/**
+ *
+ */
+INLINE void CopyOnWritePointer::
+operator = (PointerTo<CopyOnWriteObject> &&from) NOEXCEPT {
+  if (from.p() != _cow_object) {
+    CopyOnWriteObject *old_object = _cow_object;
+
+    // Steal the other's reference count, but because it is a regular pointer,
+    // we do need to include the cache reference count.
+    _cow_object = from.p();
+    _cow_object->cache_ref_only();
+    from.cheat() = NULL;
 
     if (old_object != (CopyOnWriteObject *)NULL) {
       cache_unref_delete(old_object);
@@ -262,20 +297,60 @@ operator = (To *object) {
  */
 template<class T>
 INLINE CopyOnWritePointerTo<T>::
-CopyOnWritePointerTo(CopyOnWritePointerTo<T> &&move) NOEXCEPT :
-  CopyOnWritePointer((CopyOnWritePointer &&)move)
+CopyOnWritePointerTo(CopyOnWritePointerTo<T> &&from) NOEXCEPT :
+  CopyOnWritePointer((CopyOnWritePointer &&)from)
 {
 }
 #endif  // CPPPARSER
 
+#ifndef CPPPARSER
+/**
+ *
+ */
+template<class T>
+INLINE CopyOnWritePointerTo<T>::
+CopyOnWritePointerTo(PointerTo<T> &&from) NOEXCEPT {
+  // Steal the other's reference count, but because it is a regular pointer,
+  // we do need to include the cache reference count.
+  _cow_object = from.p();
+  if (_cow_object != (CopyOnWriteObject *)NULL) {
+    _cow_object->cache_ref_only();
+  }
+  from.cheat() = NULL;
+}
+#endif  // CPPPARSER
+
+#ifndef CPPPARSER
+/**
+ *
+ */
+template<class T>
+INLINE void CopyOnWritePointerTo<T>::
+operator = (CopyOnWritePointerTo<T> &&from) NOEXCEPT {
+  CopyOnWritePointer::operator = ((CopyOnWritePointer &&)from);
+}
+#endif  // CPPPARSER
+
 #ifndef CPPPARSER
 /**
  *
  */
 template<class T>
 INLINE void CopyOnWritePointerTo<T>::
-operator = (CopyOnWritePointerTo<T> &&move) NOEXCEPT {
-  CopyOnWritePointer::operator = ((CopyOnWritePointer &&)move);
+operator = (PointerTo<T> &&from) NOEXCEPT {
+  if (from.p() != _cow_object) {
+    CopyOnWriteObject *old_object = _cow_object;
+
+    // Steal the other's reference count, but because it is a regular pointer,
+    // we do need to include the cache reference count.
+    _cow_object = from.p();
+    _cow_object->cache_ref_only();
+    from.cheat() = NULL;
+
+    if (old_object != (CopyOnWriteObject *)NULL) {
+      cache_unref_delete(old_object);
+    }
+  }
 }
 #endif  // CPPPARSER
 #endif  // USE_MOVE_SEMANTICS

+ 12 - 16
panda/src/putil/copyOnWritePointer.cxx

@@ -90,19 +90,17 @@ get_write_pointer() {
     }
 
     PT(CopyOnWriteObject) new_object = _cow_object->make_cow_copy();
+    _cow_object->CachedTypedWritableReferenceCount::cache_unref();
+    _cow_object->_lock_mutex.release();
 
-    // We can't call cache_unref_delete, because we hold the lock.
-    if (!_cow_object->CachedTypedWritableReferenceCount::cache_unref()) {
-      _cow_object->_lock_mutex.release();
-      delete _cow_object;
-    } else {
-      _cow_object->_lock_mutex.release();
-    }
+    MutexHolder holder(new_object->_lock_mutex);
     _cow_object = new_object;
-    _cow_object->cache_ref();
+    _cow_object->CachedTypedWritableReferenceCount::cache_ref();
     _cow_object->_lock_status = CopyOnWriteObject::LS_locked_write;
     _cow_object->_locking_thread = current_thread;
 
+    return new_object;
+
   } else if (_cow_object->get_cache_ref_count() > 1) {
     // No one else has it specifically read-locked, but there are other
     // CopyOnWritePointers holding the same object, so we should make our own
@@ -115,19 +113,17 @@ get_write_pointer() {
     }
 
     PT(CopyOnWriteObject) new_object = _cow_object->make_cow_copy();
+    _cow_object->CachedTypedWritableReferenceCount::cache_unref();
+    _cow_object->_lock_mutex.release();
 
-    // We can't call cache_unref_delete, because we hold the lock.
-    if (!_cow_object->CachedTypedWritableReferenceCount::cache_unref()) {
-      _cow_object->_lock_mutex.release();
-      delete _cow_object;
-    } else {
-      _cow_object->_lock_mutex.release();
-    }
+    MutexHolder holder(new_object->_lock_mutex);
     _cow_object = new_object;
-    _cow_object->cache_ref();
+    _cow_object->CachedTypedWritableReferenceCount::cache_ref();
     _cow_object->_lock_status = CopyOnWriteObject::LS_locked_write;
     _cow_object->_locking_thread = current_thread;
 
+    return new_object;
+
   } else {
     // No other thread has the pointer locked, and we're the only
     // CopyOnWritePointer with this object.  We can safely write to it without

+ 9 - 5
panda/src/putil/copyOnWritePointer.h

@@ -37,8 +37,10 @@ public:
   INLINE ~CopyOnWritePointer();
 
 #ifdef USE_MOVE_SEMANTICS
-  INLINE CopyOnWritePointer(CopyOnWritePointer &&move) NOEXCEPT;
-  INLINE void operator = (CopyOnWritePointer &&move) NOEXCEPT;
+  INLINE CopyOnWritePointer(CopyOnWritePointer &&from) NOEXCEPT;
+  INLINE CopyOnWritePointer(PointerTo<CopyOnWriteObject> &&from) NOEXCEPT;
+  INLINE void operator = (CopyOnWritePointer &&from) NOEXCEPT;
+  INLINE void operator = (PointerTo<CopyOnWriteObject> &&from) NOEXCEPT;
 #endif
 
   INLINE bool operator == (const CopyOnWritePointer &other) const;
@@ -61,7 +63,7 @@ public:
   INLINE bool test_ref_count_integrity() const;
   INLINE bool test_ref_count_nonzero() const;
 
-private:
+protected:
   CopyOnWriteObject *_cow_object;
 };
 
@@ -84,8 +86,10 @@ public:
   INLINE void operator = (To *object);
 
 #ifdef USE_MOVE_SEMANTICS
-  INLINE CopyOnWritePointerTo(CopyOnWritePointerTo &&move) NOEXCEPT;
-  INLINE void operator = (CopyOnWritePointerTo &&move) NOEXCEPT;
+  INLINE CopyOnWritePointerTo(CopyOnWritePointerTo &&from) NOEXCEPT;
+  INLINE CopyOnWritePointerTo(PointerTo<T> &&from) NOEXCEPT;
+  INLINE void operator = (CopyOnWritePointerTo &&from) NOEXCEPT;
+  INLINE void operator = (PointerTo<T> &&from) NOEXCEPT;
 #endif
 
 #ifdef COW_THREADED

+ 66 - 0
panda/src/vision/webcamVideoV4L.cxx

@@ -22,6 +22,72 @@
 #include <sys/ioctl.h>
 #include <linux/videodev2.h>
 
+#ifndef CPPPARSER
+#ifndef VIDIOC_ENUM_FRAMESIZES
+enum v4l2_frmsizetypes {
+  V4L2_FRMSIZE_TYPE_DISCRETE = 1,
+  V4L2_FRMSIZE_TYPE_CONTINUOUS = 2,
+  V4L2_FRMSIZE_TYPE_STEPWISE = 3,
+};
+
+struct v4l2_frmsize_discrete {
+  __u32 width;
+  __u32 height;
+};
+
+struct v4l2_frmsize_stepwise {
+  __u32 min_width;
+  __u32 max_width;
+  __u32 step_width;
+  __u32 min_height;
+  __u32 max_height;
+  __u32 step_height;
+};
+
+struct v4l2_frmsizeenum {
+  __u32 index;
+  __u32 pixel_format;
+  __u32 type;
+  union {
+    struct v4l2_frmsize_discrete discrete;
+    struct v4l2_frmsize_stepwise stepwise;
+  };
+  __u32 reserved[2];
+};
+
+#define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
+#endif
+
+#ifndef VIDIOC_ENUM_FRAMEINTERVALS
+enum v4l2_frmivaltypes {
+  V4L2_FRMIVAL_TYPE_DISCRETE = 1,
+  V4L2_FRMIVAL_TYPE_CONTINUOUS = 2,
+  V4L2_FRMIVAL_TYPE_STEPWISE = 3,
+};
+
+struct v4l2_frmival_stepwise {
+  struct v4l2_fract min;
+  struct v4l2_fract max;
+  struct v4l2_fract step;
+};
+
+struct v4l2_frmivalenum {
+  __u32 index;
+  __u32 pixel_format;
+  __u32 width;
+  __u32 height;
+  __u32 type;
+  union {
+    struct v4l2_fract               discrete;
+    struct v4l2_frmival_stepwise    stepwise;
+  };
+  __u32 reserved[2];
+};
+
+#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
+#endif
+#endif
+
 TypeHandle WebcamVideoV4L::_type_handle;
 
 /**

+ 8 - 7
panda/src/windisplay/winGraphicsWindow.cxx

@@ -2570,7 +2570,7 @@ ButtonMap *WinGraphicsWindow::
 get_keyboard_map() const {
   ButtonMap *map = new ButtonMap;
 
-  char text[256];
+  wchar_t text[256];
   UINT vsc = 0;
   unsigned short ex_vsc[] = {0x57, 0x58,
     0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
@@ -2608,14 +2608,15 @@ get_keyboard_map() const {
 
       UINT vk = MapVirtualKeyA(vsc, MAPVK_VSC_TO_VK_EX);
       button = lookup_key(vk);
-      if (button == ButtonHandle::none()) {
-        continue;
-      }
+      //if (button == ButtonHandle::none()) {
+      //  continue;
+      //}
     }
 
-    int len = GetKeyNameTextA(lparam, text, 256);
-    string label (text, len);
-    map->map_button(raw_button, button, label);
+    int len = GetKeyNameTextW(lparam, text, 256);
+    TextEncoder enc;
+    enc.set_wtext(wstring(text, len));
+    map->map_button(raw_button, button, enc.get_text());
   }
 
   return map;

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

@@ -60,6 +60,11 @@ ConfigVariableInt x_wheel_right_button
           "mouse button number does the system report when one scrolls "
           "to the right?"));
 
+ConfigVariableInt x_cursor_size
+("x-cursor-size", -1,
+ PRC_DESC("This sets the cursor size when using XCursor to change the mouse "
+          "cursor.  The default is to use the default size for the display."));
+
 ConfigVariableString x_wm_class_name
 ("x-wm-class-name", "",
  PRC_DESC("Specify the value to use for the res_name field of the window's "

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

@@ -32,6 +32,7 @@ extern ConfigVariableInt x_wheel_down_button;
 extern ConfigVariableInt x_wheel_left_button;
 extern ConfigVariableInt x_wheel_right_button;
 
+extern ConfigVariableInt x_cursor_size;
 extern ConfigVariableString x_wm_class_name;
 extern ConfigVariableString x_wm_class;
 

+ 32 - 0
panda/src/x11display/x11GraphicsPipe.I

@@ -57,6 +57,38 @@ get_hidden_cursor() {
   return _hidden_cursor;
 }
 
+/**
+ * Returns true if relative mouse mode is supported on this display.
+ */
+INLINE bool x11GraphicsPipe::
+supports_relative_mouse() const {
+  return (_XF86DGADirectVideo != NULL);
+}
+
+/**
+ * Enables relative mouse mode for this display.  Returns false if unsupported.
+ */
+INLINE bool x11GraphicsPipe::
+enable_relative_mouse() {
+  if (_XF86DGADirectVideo != NULL) {
+    x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n";
+    _XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse);
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Disables relative mouse mode for this display.
+ */
+INLINE void x11GraphicsPipe::
+disable_relative_mouse() {
+  if (_XF86DGADirectVideo != NULL) {
+    x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n";
+    _XF86DGADirectVideo(_display, _screen, 0);
+  }
+}
+
 /**
  * Globally disables the printing of error messages that are raised by the X11
  * system, for instance in order to test whether a particular X11 operation

+ 113 - 25
panda/src/x11display/x11GraphicsPipe.cxx

@@ -17,6 +17,8 @@
 #include "frameBufferProperties.h"
 #include "displayInformation.h"
 
+#include <dlfcn.h>
+
 TypeHandle x11GraphicsPipe::_type_handle;
 
 bool x11GraphicsPipe::_error_handlers_installed = false;
@@ -31,7 +33,11 @@ LightReMutex x11GraphicsPipe::_x_mutex;
  *
  */
 x11GraphicsPipe::
-x11GraphicsPipe(const string &display) {
+x11GraphicsPipe(const string &display) :
+  _have_xrandr(false),
+  _xcursor_size(-1),
+  _XF86DGADirectVideo(NULL) {
+
   string display_spec = display;
   if (display_spec.empty()) {
     display_spec = display_cfg;
@@ -86,34 +92,116 @@ x11GraphicsPipe(const string &display) {
   _display_height = DisplayHeight(_display, _screen);
   _is_valid = true;
 
-#ifdef HAVE_XRANDR
-  // Use Xrandr to fill in the supported resolution list.
-  int num_sizes, num_rates;
-  XRRScreenSize *xrrs;
-  xrrs = XRRSizes(_display, 0, &num_sizes);
-  _display_information->_total_display_modes = 0;
-  for (int i = 0; i < num_sizes; ++i) {
-    XRRRates(_display, 0, i, &num_rates);
-    _display_information->_total_display_modes += num_rates;
+  // Dynamically load the xf86dga extension.
+  void *xf86dga = dlopen("libXxf86dga.so.1", RTLD_NOW | RTLD_LOCAL);
+  if (xf86dga != NULL) {
+    pfn_XF86DGAQueryVersion _XF86DGAQueryVersion = (pfn_XF86DGAQueryVersion)dlsym(xf86dga, "XF86DGAQueryVersion");
+    _XF86DGADirectVideo = (pfn_XF86DGADirectVideo)dlsym(xf86dga, "XF86DGADirectVideo");
+
+    int major_ver, minor_ver;
+    if (_XF86DGAQueryVersion == NULL || _XF86DGADirectVideo == NULL) {
+      x11display_cat.warning()
+        << "libXxf86dga.so.1 does not provide required functions; relative mouse mode will not work.\n";
+
+    } else if (!_XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
+      _XF86DGADirectVideo = NULL;
+    }
+  } else {
+    _XF86DGADirectVideo = NULL;
+    if (x11display_cat.is_debug()) {
+      x11display_cat.debug()
+        << "cannot dlopen libXxf86dga.so.1; cursor changing will not work.\n";
+    }
+  }
+
+  // Dynamically load the XCursor extension.
+  void *xcursor = dlopen("libXcursor.so.1", RTLD_NOW | RTLD_LOCAL);
+  if (xcursor != NULL) {
+    pfn_XcursorGetDefaultSize _XcursorGetDefaultSize = (pfn_XcursorGetDefaultSize)dlsym(xcursor, "XcursorGetDefaultSize");
+    _XcursorXcFileLoadImages = (pfn_XcursorXcFileLoadImages)dlsym(xcursor, "XcursorXcFileLoadImages");
+    _XcursorImagesLoadCursor = (pfn_XcursorImagesLoadCursor)dlsym(xcursor, "XcursorImagesLoadCursor");
+    _XcursorImagesDestroy = (pfn_XcursorImagesDestroy)dlsym(xcursor, "XcursorImagesDestroy");
+    _XcursorImageCreate = (pfn_XcursorImageCreate)dlsym(xcursor, "XcursorImageCreate");
+    _XcursorImageLoadCursor = (pfn_XcursorImageLoadCursor)dlsym(xcursor, "XcursorImageLoadCursor");
+    _XcursorImageDestroy = (pfn_XcursorImageDestroy)dlsym(xcursor, "XcursorImageDestroy");
+
+    if (_XcursorGetDefaultSize == NULL || _XcursorXcFileLoadImages == NULL ||
+        _XcursorImagesLoadCursor == NULL || _XcursorImagesDestroy == NULL ||
+        _XcursorImageCreate == NULL || _XcursorImageLoadCursor == NULL ||
+        _XcursorImageDestroy == NULL) {
+      _xcursor_size = -1;
+      x11display_cat.warning()
+        << "libXcursor.so.1 does not provide required functions; cursor changing will not work.\n";
+
+    } else if (x_cursor_size.get_value() >= 0) {
+      _xcursor_size = x_cursor_size;
+    } else {
+      _xcursor_size = _XcursorGetDefaultSize(_display);
+    }
+  } else {
+    _xcursor_size = -1;
+    if (x11display_cat.is_debug()) {
+      x11display_cat.debug()
+        << "cannot dlopen libXcursor.so.1; cursor changing will not work.\n";
+    }
+  }
+
+  // Dynamically load the XRandr extension.
+  void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL);
+  if (xrandr != NULL) {
+    pfn_XRRQueryExtension _XRRQueryExtension = (pfn_XRRQueryExtension)dlsym(xrandr, "XRRQueryExtension");
+    _XRRSizes = (pfn_XRRSizes)dlsym(xrandr, "XRRSizes");
+    _XRRRates = (pfn_XRRRates)dlsym(xrandr, "XRRRates");
+    _XRRGetScreenInfo = (pfn_XRRGetScreenInfo)dlsym(xrandr, "XRRGetScreenInfo");
+    _XRRConfigCurrentConfiguration = (pfn_XRRConfigCurrentConfiguration)dlsym(xrandr, "XRRConfigCurrentConfiguration");
+    _XRRSetScreenConfig = (pfn_XRRSetScreenConfig)dlsym(xrandr, "XRRSetScreenConfig");
+
+    if (_XRRQueryExtension == NULL || _XRRSizes == NULL || _XRRRates == NULL ||
+        _XRRGetScreenInfo == NULL || _XRRConfigCurrentConfiguration == NULL ||
+        _XRRSetScreenConfig == NULL) {
+      _have_xrandr = false;
+      x11display_cat.warning()
+        << "libXrandr.so.2 does not provide required functions; resolution setting will not work.\n";
+    } else {
+      int event, error;
+      _have_xrandr = _XRRQueryExtension(_display, &event, &error);
+    }
+  } else {
+    _have_xrandr = false;
+    if (x11display_cat.is_debug()) {
+      x11display_cat.debug()
+        << "cannot dlopen libXrandr.so.2; resolution setting will not work.\n";
+    }
   }
 
-  short *rates;
-  short counter = 0;
-  _display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
-  for (int i = 0; i < num_sizes; ++i) {
-    int num_rates;
-    rates = XRRRates(_display, 0, i, &num_rates);
-    for (int j = 0; j < num_rates; ++j) {
-      DisplayMode* dm = _display_information->_display_mode_array + counter;
-      dm->width = xrrs[i].width;
-      dm->height = xrrs[i].height;
-      dm->refresh_rate = rates[j];
-      dm->bits_per_pixel = -1;
-      dm->fullscreen_only = false;
-      ++counter;
+  // Use Xrandr to fill in the supported resolution list.
+  if (_have_xrandr) {
+    int num_sizes, num_rates;
+    XRRScreenSize *xrrs;
+    xrrs = _XRRSizes(_display, 0, &num_sizes);
+    _display_information->_total_display_modes = 0;
+    for (int i = 0; i < num_sizes; ++i) {
+      _XRRRates(_display, 0, i, &num_rates);
+      _display_information->_total_display_modes += num_rates;
+    }
+
+    short *rates;
+    short counter = 0;
+    _display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
+    for (int i = 0; i < num_sizes; ++i) {
+      int num_rates;
+      rates = _XRRRates(_display, 0, i, &num_rates);
+      for (int j = 0; j < num_rates; ++j) {
+        DisplayMode* dm = _display_information->_display_mode_array + counter;
+        dm->width = xrrs[i].width;
+        dm->height = xrrs[i].height;
+        dm->refresh_rate = rates[j];
+        dm->bits_per_pixel = -1;
+        dm->fullscreen_only = false;
+        ++counter;
+      }
     }
   }
-#endif
 
   // Connect to an input method for supporting international text entry.
   _im = XOpenIM(_display, NULL, NULL, NULL);

+ 56 - 0
panda/src/x11display/x11GraphicsPipe.h

@@ -21,6 +21,22 @@
 #include "lightReMutex.h"
 #include "windowHandle.h"
 #include "get_x11.h"
+#include "config_x11display.h"
+
+// Excerpt the few definitions we need for the extensions.
+#define XF86DGADirectMouse 0x0004
+
+typedef struct _XcursorFile XcursorFile;
+typedef struct _XcursorImage XcursorImage;
+typedef struct _XcursorImages XcursorImages;
+
+typedef unsigned short Rotation;
+typedef unsigned short SizeID;
+typedef struct _XRRScreenConfiguration XRRScreenConfiguration;
+typedef struct {
+  int width, height;
+  int mwidth, mheight;
+} XRRScreenSize;
 
 class FrameBufferProperties;
 
@@ -40,6 +56,10 @@ public:
 
   INLINE X11_Cursor get_hidden_cursor();
 
+  INLINE bool supports_relative_mouse() const;
+  INLINE bool enable_relative_mouse();
+  INLINE void disable_relative_mouse();
+
   static INLINE int disable_x_error_messages();
   static INLINE int enable_x_error_messages();
   static INLINE int get_x_error_count();
@@ -61,6 +81,38 @@ public:
   Atom _net_wm_state_add;
   Atom _net_wm_state_remove;
 
+  // Extension functions.
+  typedef int (*pfn_XcursorGetDefaultSize)(X11_Display *);
+  typedef XcursorImages *(*pfn_XcursorXcFileLoadImages)(XcursorFile *, int);
+  typedef X11_Cursor (*pfn_XcursorImagesLoadCursor)(X11_Display *, const XcursorImages *);
+  typedef void (*pfn_XcursorImagesDestroy)(XcursorImages *);
+  typedef XcursorImage *(*pfn_XcursorImageCreate)(int, int);
+  typedef X11_Cursor (*pfn_XcursorImageLoadCursor)(X11_Display *, const XcursorImage *);
+  typedef void (*pfn_XcursorImageDestroy)(XcursorImage *);
+
+  int _xcursor_size;
+  pfn_XcursorXcFileLoadImages _XcursorXcFileLoadImages;
+  pfn_XcursorImagesLoadCursor _XcursorImagesLoadCursor;
+  pfn_XcursorImagesDestroy _XcursorImagesDestroy;
+  pfn_XcursorImageCreate _XcursorImageCreate;
+  pfn_XcursorImageLoadCursor _XcursorImageLoadCursor;
+  pfn_XcursorImageDestroy _XcursorImageDestroy;
+
+  typedef Bool (*pfn_XRRQueryExtension)(X11_Display *, int*, int*);
+  typedef XRRScreenSize *(*pfn_XRRSizes)(X11_Display*, int, int*);
+  typedef short *(*pfn_XRRRates)(X11_Display*, int, int, int*);
+  typedef XRRScreenConfiguration *(*pfn_XRRGetScreenInfo)(X11_Display*, X11_Window);
+  typedef SizeID (*pfn_XRRConfigCurrentConfiguration)(XRRScreenConfiguration*, Rotation*);
+  typedef Status (*pfn_XRRSetScreenConfig)(X11_Display*, XRRScreenConfiguration *,
+                                        Drawable, int, Rotation, Time);
+
+  bool _have_xrandr;
+  pfn_XRRSizes _XRRSizes;
+  pfn_XRRRates _XRRRates;
+  pfn_XRRGetScreenInfo _XRRGetScreenInfo;
+  pfn_XRRConfigCurrentConfiguration _XRRConfigCurrentConfiguration;
+  pfn_XRRSetScreenConfig _XRRSetScreenConfig;
+
 protected:
   X11_Display *_display;
   int _screen;
@@ -69,6 +121,10 @@ protected:
 
   X11_Cursor _hidden_cursor;
 
+  typedef Bool (*pfn_XF86DGAQueryVersion)(X11_Display *, int*, int*);
+  typedef Status (*pfn_XF86DGADirectVideo)(X11_Display *, int, int);
+  pfn_XF86DGADirectVideo _XF86DGADirectVideo;
+
 private:
   void make_hidden_cursor();
   void release_hidden_cursor();

+ 72 - 68
panda/src/x11display/x11GraphicsWindow.cxx

@@ -38,7 +38,24 @@
 #include <linux/input.h>
 #endif
 
-#ifdef HAVE_XCURSOR
+struct _XcursorFile {
+  void *closure;
+  int (*read)(XcursorFile *, unsigned char *, int);
+  int (*write)(XcursorFile *, unsigned char *, int);
+  int (*seek)(XcursorFile *, long, int);
+};
+
+typedef struct _XcursorImage {
+  unsigned int version;
+  unsigned int size;
+  unsigned int width;
+  unsigned int height;
+  unsigned int xhot;
+  unsigned int yhot;
+  unsigned int delay;
+  unsigned int *pixels;
+} XcursorImage;
+
 static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) {
   istream* str = (istream*) file->closure;
   str->read((char*) buf, len);
@@ -66,7 +83,6 @@ static int xcursor_seek(XcursorFile *file, long offset, int whence) {
 
   return str->tellg();
 }
-#endif
 
 TypeHandle x11GraphicsWindow::_type_handle;
 
@@ -92,14 +108,14 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   _xwindow = (X11_Window)NULL;
   _ic = (XIC)NULL;
   _visual_info = NULL;
-
-#ifdef HAVE_XRANDR
   _orig_size_id = -1;
-  int event, error;
-  _have_xrandr = XRRQueryExtension(_display, &event, &error);
-#else
-  _have_xrandr = false;
-#endif
+
+  if (x11_pipe->_have_xrandr) {
+    // We may still need these functions after the pipe is already destroyed,
+    // so we copy them into the x11GraphicsWindow.
+    _XRRGetScreenInfo = x11_pipe->_XRRGetScreenInfo;
+    _XRRSetScreenConfig = x11_pipe->_XRRSetScreenConfig;
+  }
 
   _awaiting_configure = false;
   _dga_mouse_enabled = false;
@@ -474,10 +490,9 @@ set_properties_now(WindowProperties &properties) {
 
   if (is_fullscreen != want_fullscreen || (is_fullscreen && properties.has_size())) {
     if (want_fullscreen) {
-      if (_have_xrandr) {
-#ifdef HAVE_XRANDR
-        XRRScreenConfiguration* conf = XRRGetScreenInfo(_display, x11_pipe->get_root());
-        SizeID old_size_id = XRRConfigCurrentConfiguration(conf, &_orig_rotation);
+      if (x11_pipe->_have_xrandr) {
+        XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
+        SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
         SizeID new_size_id = (SizeID) -1;
         int num_sizes = 0, reqsizex, reqsizey;
         if (properties.has_size()) {
@@ -488,7 +503,7 @@ set_properties_now(WindowProperties &properties) {
           reqsizey = _properties.get_y_size();
         }
         XRRScreenSize *xrrs;
-        xrrs = XRRSizes(_display, 0, &num_sizes);
+        xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
         for (int i = 0; i < num_sizes; ++i) {
           if (xrrs[i].width == reqsizex &&
               xrrs[i].height == reqsizey) {
@@ -502,14 +517,13 @@ set_properties_now(WindowProperties &properties) {
         } else {
           if (new_size_id != old_size_id) {
 
-            XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
+            _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
             if (_orig_size_id == (SizeID) -1) {
               // Remember the original resolution so we can switch back to it.
               _orig_size_id = old_size_id;
             }
           }
         }
-#endif
       } else {
         // If we don't have Xrandr support, we fake the fullscreen support by
         // setting the window size to the desktop size.
@@ -517,15 +531,13 @@ set_properties_now(WindowProperties &properties) {
                             x11_pipe->get_display_height());
       }
     } else {
-#ifdef HAVE_XRANDR
       // Change the resolution back to what it was.  Don't remove the SizeID
       // typecast!
-      if (_have_xrandr && _orig_size_id != (SizeID) -1) {
-        XRRScreenConfiguration* conf = XRRGetScreenInfo(_display, x11_pipe->get_root());
-        XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), _orig_size_id, _orig_rotation, CurrentTime);
+      if (_orig_size_id != (SizeID) -1) {
+        XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
+        _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), _orig_size_id, _orig_rotation, CurrentTime);
         _orig_size_id = (SizeID) -1;
       }
-#endif
       // Set the origin back to what it was
       if (!properties.has_origin() && _properties.has_origin()) {
         properties.set_origin(_properties.get_x_origin(), _properties.get_y_origin());
@@ -686,23 +698,17 @@ set_properties_now(WindowProperties &properties) {
     switch (properties.get_mouse_mode()) {
     case WindowProperties::M_absolute:
       XUngrabPointer(_display, CurrentTime);
-#ifdef HAVE_XF86DGA
       if (_dga_mouse_enabled) {
-        x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n";
-        XF86DGADirectVideo(_display, _screen, 0);
+        x11_pipe->disable_relative_mouse();
         _dga_mouse_enabled = false;
       }
-#endif
       _properties.set_mouse_mode(WindowProperties::M_absolute);
       properties.clear_mouse_mode();
       break;
 
     case WindowProperties::M_relative:
-#ifdef HAVE_XF86DGA
       if (!_dga_mouse_enabled) {
-        int major_ver, minor_ver;
-        if (XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
-
+        if (x11_pipe->supports_relative_mouse()) {
           X11_Cursor cursor = None;
           if (_properties.get_cursor_hidden()) {
             x11GraphicsPipe *x11_pipe;
@@ -714,8 +720,7 @@ set_properties_now(WindowProperties &properties) {
               GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
             x11display_cat.error() << "Failed to grab pointer!\n";
           } else {
-            x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n";
-            XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse);
+            x11_pipe->enable_relative_mouse();
 
             _properties.set_mouse_mode(WindowProperties::M_relative);
             properties.clear_mouse_mode();
@@ -730,25 +735,24 @@ set_properties_now(WindowProperties &properties) {
             _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y);
           }
         } else {
-          x11display_cat.info() << "XF86DGA extension not available\n";
+          x11display_cat.info()
+            << "XF86DGA extension not available, cannot enable relative mouse mode\n";
           _dga_mouse_enabled = false;
         }
       }
-#endif
       break;
 
     case WindowProperties::M_confined:
       {
-#ifdef HAVE_XF86DGA
+        x11GraphicsPipe *x11_pipe;
+        DCAST_INTO_V(x11_pipe, _pipe);
+
         if (_dga_mouse_enabled) {
-          XF86DGADirectVideo(_display, _screen, 0);
+          x11_pipe->disable_relative_mouse();
           _dga_mouse_enabled = false;
         }
-#endif
         X11_Cursor cursor = None;
         if (_properties.get_cursor_hidden()) {
-          x11GraphicsPipe *x11_pipe;
-          DCAST_INTO_V(x11_pipe, _pipe);
           cursor = x11_pipe->get_hidden_cursor();
         }
 
@@ -813,10 +817,9 @@ close_window() {
     XFlush(_display);
   }
 
-#ifdef HAVE_XRANDR
   // Change the resolution back to what it was.  Don't remove the SizeID
   // typecast!
-  if (_have_xrandr && _orig_size_id != (SizeID) -1) {
+  if (_orig_size_id != (SizeID) -1) {
     X11_Window root;
     if (_pipe != NULL) {
       x11GraphicsPipe *x11_pipe;
@@ -827,11 +830,10 @@ close_window() {
       // closed.  Oh well, let's get the root window by ourselves.
       root = RootWindow(_display, _screen);
     }
-    XRRScreenConfiguration* conf = XRRGetScreenInfo(_display, root);
-    XRRSetScreenConfig(_display, conf, root, _orig_size_id, _orig_rotation, CurrentTime);
+    XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, root);
+    _XRRSetScreenConfig(_display, conf, root, _orig_size_id, _orig_rotation, CurrentTime);
     _orig_size_id = -1;
   }
-#endif
 
   GraphicsWindow::close_window();
 }
@@ -859,15 +861,14 @@ open_window() {
     _properties.set_size(100, 100);
   }
 
-#ifdef HAVE_XRANDR
-  if (_properties.get_fullscreen() && _have_xrandr) {
-    XRRScreenConfiguration* conf = XRRGetScreenInfo(_display, x11_pipe->get_root());
+  if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) {
+    XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
     if (_orig_size_id == (SizeID) -1) {
-      _orig_size_id = XRRConfigCurrentConfiguration(conf, &_orig_rotation);
+      _orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
     }
     int num_sizes, new_size_id = -1;
     XRRScreenSize *xrrs;
-    xrrs = XRRSizes(_display, 0, &num_sizes);
+    xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
     for (int i = 0; i < num_sizes; ++i) {
       if (xrrs[i].width == _properties.get_x_size() &&
           xrrs[i].height == _properties.get_y_size()) {
@@ -882,12 +883,11 @@ open_window() {
       return false;
     }
     if (new_size_id != _orig_size_id) {
-      XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
+      _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
     } else {
       _orig_size_id = -1;
     }
   }
-#endif
 
   X11_Window parent_window = x11_pipe->get_root();
   WindowHandle *window_handle = _properties.get_parent_window();
@@ -2081,11 +2081,15 @@ check_event(X11_Display *display, XEvent *event, char *arg) {
  */
 X11_Cursor x11GraphicsWindow::
 get_cursor(const Filename &filename) {
-#ifndef HAVE_XCURSOR
-  x11display_cat.info()
-    << "XCursor support not enabled in build; cannot change mouse cursor.\n";
-  return None;
-#else  // HAVE_XCURSOR
+  x11GraphicsPipe *x11_pipe;
+  DCAST_INTO_R(x11_pipe, _pipe, None);
+
+  if (x11_pipe->_xcursor_size == -1) {
+    x11display_cat.info()
+      << "libXcursor.so.1 not available; cannot change mouse cursor.\n";
+    return None;
+  }
+
   // First, look for the unresolved filename in our index.
   pmap<Filename, X11_Cursor>::iterator fi = _cursor_filenames.find(filename);
   if (fi != _cursor_filenames.end()) {
@@ -2135,10 +2139,10 @@ get_cursor(const Filename &filename) {
     xcfile.write = &xcursor_write;
     xcfile.seek = &xcursor_seek;
 
-    XcursorImages *images = XcursorXcFileLoadImages(&xcfile, XcursorGetDefaultSize(_display));
+    XcursorImages *images = x11_pipe->_XcursorXcFileLoadImages(&xcfile, x11_pipe->_xcursor_size);
     if (images != NULL) {
-      h = XcursorImagesLoadCursor(_display, images);
-      XcursorImagesDestroy(images);
+      h = x11_pipe->_XcursorImagesLoadCursor(_display, images);
+      x11_pipe->_XcursorImagesDestroy(images);
     }
 
   } else if (memcmp(magic, "\0\0\1\0", 4) == 0
@@ -2159,18 +2163,19 @@ get_cursor(const Filename &filename) {
 
   _cursor_filenames[resolved] = h;
   return h;
-#endif  // HAVE_XCURSOR
 }
 
-#ifdef HAVE_XCURSOR
 /**
  * Reads a Windows .ico or .cur file from the indicated stream and returns it
  * as an X11 Cursor.  If the file cannot be loaded, returns None.
  */
 X11_Cursor x11GraphicsWindow::
 read_ico(istream &ico) {
- // Local structs, this is just POD, make input easier
- typedef struct {
+  x11GraphicsPipe *x11_pipe;
+  DCAST_INTO_R(x11_pipe, _pipe, None);
+
+  // Local structs, this is just POD, make input easier
+  typedef struct {
     uint16_t reserved, type, count;
   } IcoHeader;
 
@@ -2204,7 +2209,7 @@ read_ico(istream &ico) {
   XcursorImage *image = NULL;
   X11_Cursor ret = None;
 
-  int def_size = XcursorGetDefaultSize(_display);
+  int def_size = x11_pipe->_xcursor_size;
 
   // Get our header, note that ICO = type 1 and CUR = type 2.
   ico.read(reinterpret_cast<char *>(&header), sizeof(IcoHeader));
@@ -2240,7 +2245,7 @@ read_ico(istream &ico) {
     }
     img.set_maxval(255);
 
-    image = XcursorImageCreate(img.get_x_size(), img.get_y_size());
+    image = x11_pipe->_XcursorImageCreate(img.get_x_size(), img.get_y_size());
 
     xel *ptr = img.get_array();
     xelval *alpha = img.get_alpha_array();
@@ -2285,7 +2290,7 @@ read_ico(istream &ico) {
     ico.read(andBmp, andBmpSize);
     if (!ico.good()) goto cleanup;
 
-    image = XcursorImageCreate(infoHeader.width, infoHeader.height / 2);
+    image = x11_pipe->_XcursorImageCreate(infoHeader.width, infoHeader.height / 2);
 
     // Support all the formats that GIMP supports.
     switch (bitsPerPixel) {
@@ -2370,10 +2375,10 @@ read_ico(istream &ico) {
     image->yhot = 0;
   }
 
-  ret = XcursorImageLoadCursor(_display, image);
+  ret = x11_pipe->_XcursorImageLoadCursor(_display, image);
 
 cleanup:
-  XcursorImageDestroy(image);
+  x11_pipe->_XcursorImageDestroy(image);
   delete[] entries;
   delete[] palette;
   delete[] xorBmp;
@@ -2381,4 +2386,3 @@ cleanup:
 
   return ret;
 }
-#endif  // HAVE_XCURSOR

+ 3 - 11
panda/src/x11display/x11GraphicsWindow.h

@@ -20,11 +20,6 @@
 #include "graphicsWindow.h"
 #include "buttonHandle.h"
 
-#ifdef HAVE_XRANDR
-typedef unsigned short Rotation;
-typedef unsigned short SizeID;
-#endif
-
 /**
  * Interfaces to the X11 window system.
  */
@@ -76,9 +71,7 @@ protected:
 
 private:
   X11_Cursor get_cursor(const Filename &filename);
-#ifdef HAVE_XCURSOR
   X11_Cursor read_ico(istream &ico);
-#endif
 
 protected:
   X11_Display *_display;
@@ -87,12 +80,8 @@ protected:
   Colormap _colormap;
   XIC _ic;
   XVisualInfo *_visual_info;
-
-  bool _have_xrandr;
-#ifdef HAVE_XRANDR
   Rotation _orig_rotation;
   SizeID _orig_size_id;
-#endif
 
   LVecBase2i _fixed_size;
 
@@ -109,6 +98,9 @@ protected:
   };
   pvector<MouseDeviceInfo> _mouse_device_info;
 
+  x11GraphicsPipe::pfn_XRRGetScreenInfo _XRRGetScreenInfo;
+  x11GraphicsPipe::pfn_XRRSetScreenConfig _XRRSetScreenConfig;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;