Browse Source

Merge branch 'master' into deploy-ng

rdb 7 years ago
parent
commit
c28045990c
91 changed files with 1999 additions and 696 deletions
  1. 38 6
      README.md
  2. 0 3
      dtool/src/dtoolbase/typeHandle.cxx
  3. 0 3
      dtool/src/dtoolbase/typeHandle.h
  4. 0 31
      dtool/src/dtoolutil/dSearchPath.cxx
  5. 7 4
      dtool/src/dtoolutil/dSearchPath.h
  6. 1 0
      dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx
  7. 1 1
      dtool/src/dtoolutil/stringDecoder.I
  8. 69 10
      dtool/src/dtoolutil/stringDecoder.cxx
  9. 9 6
      dtool/src/dtoolutil/stringDecoder.h
  10. 36 6
      dtool/src/dtoolutil/textEncoder.I
  11. 73 19
      dtool/src/dtoolutil/textEncoder.cxx
  12. 32 3
      dtool/src/dtoolutil/textEncoder.h
  13. 30 0
      dtool/src/dtoolutil/textEncoder_ext.I
  14. 164 0
      dtool/src/dtoolutil/textEncoder_ext.cxx
  15. 50 0
      dtool/src/dtoolutil/textEncoder_ext.h
  16. 1 1
      dtool/src/interrogate/interrogateBuilder.cxx
  17. 2 2
      dtool/src/interrogatedb/py_panda.cxx
  18. 3 0
      dtool/src/parser-inc/dirent.h
  19. 1 0
      dtool/src/parser-inc/sys/inotify.h
  20. 3 0
      dtool/src/parser-inc/sys/ioctl.h
  21. 3 0
      dtool/src/parser-inc/sys/mman.h
  22. 1 0
      dtool/src/parser-inc/sys/select.h
  23. 1 0
      dtool/src/parser-inc/sys/sysinfo.h
  24. 37 0
      dtool/src/prc/configDeclaration.cxx
  25. 3 0
      dtool/src/prc/configDeclaration.h
  26. 14 2
      dtool/src/prc/configVariableBool.cxx
  27. 1 9
      dtool/src/prc/configVariableFilename.cxx
  28. 25 5
      dtool/src/prc/configVariableSearchPath.I
  29. 3 10
      dtool/src/prc/configVariableSearchPath.cxx
  30. 4 3
      dtool/src/prc/configVariableSearchPath.h
  31. 5 0
      makepanda/confauto.in
  32. 7 4
      makepanda/makepanda.py
  33. 2 2
      panda/src/chan/partBundle.h
  34. 1 0
      panda/src/cocoadisplay/cocoaPandaApp.mm
  35. 9 2
      panda/src/display/graphicsEngine.cxx
  36. 12 1
      panda/src/display/graphicsStateGuardian.cxx
  37. 1 0
      panda/src/display/graphicsWindowProc.h
  38. 32 21
      panda/src/display/standardMunger.cxx
  39. 4 2
      panda/src/display/standardMunger.h
  40. 2 0
      panda/src/display/subprocessWindow.cxx
  41. 0 31
      panda/src/dxgsg9/dxGeomMunger9.I
  42. 60 0
      panda/src/dxgsg9/dxGeomMunger9.cxx
  43. 1 1
      panda/src/dxgsg9/dxGeomMunger9.h
  44. 42 2
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  45. 2 6
      panda/src/dxgsg9/dxGraphicsStateGuardian9.h
  46. 23 0
      panda/src/dxgsg9/dxShaderContext9.cxx
  47. 4 1
      panda/src/dxgsg9/wdxGraphicsWindow9.cxx
  48. 1 1
      panda/src/egg/eggMesher.h
  49. 1 1
      panda/src/egg/eggMesherEdge.h
  50. 1 1
      panda/src/egg/eggMesherFanMaker.h
  51. 1 1
      panda/src/egg/eggMesherStrip.h
  52. 1 1
      panda/src/egg2pg/eggBinner.h
  53. 1 1
      panda/src/egg2pg/eggLoader.h
  54. 1 1
      panda/src/egg2pg/eggRenderState.h
  55. 1 1
      panda/src/egg2pg/eggSaver.h
  56. 21 0
      panda/src/event/asyncTask.h
  57. 0 7
      panda/src/event/pythonTask.h
  58. 2 1
      panda/src/express/pointerToArray_ext.I
  59. 5 5
      panda/src/express/pointerToArray_ext.h
  60. 11 9
      panda/src/glstuff/glCgShaderContext_src.cxx
  61. 7 0
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  62. 9 2
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  63. 2 2
      panda/src/glstuff/glShaderContext_src.cxx
  64. 1 1
      panda/src/gobj/textureStage.cxx
  65. 10 0
      panda/src/grutil/shaderTerrainMesh.I
  66. 3 0
      panda/src/grutil/shaderTerrainMesh.cxx
  67. 3 0
      panda/src/grutil/shaderTerrainMesh.h
  68. 6 8
      panda/src/mathutil/mersenne.h
  69. 17 0
      panda/src/movies/movieTypeRegistry.cxx
  70. 3 0
      panda/src/movies/movieTypeRegistry.h
  71. 1 1
      panda/src/osxdisplay/osxGraphicsStateGuardian.h
  72. 0 1
      panda/src/particlesystem/baseParticle.cxx
  73. 0 2
      panda/src/particlesystem/baseParticle.h
  74. 6 6
      panda/src/pgraph/colorAttrib.cxx
  75. 1 1
      panda/src/pgraph/colorAttrib.h
  76. 5 5
      panda/src/pgraph/colorScaleAttrib.cxx
  77. 1 1
      panda/src/pgraph/loader.cxx
  78. 16 7
      panda/src/pgraphnodes/shaderGenerator.cxx
  79. 2 0
      panda/src/pipeline/conditionVarSpinlockImpl.cxx
  80. 2 0
      panda/src/pipeline/reMutexSpinlockImpl.cxx
  81. 133 100
      panda/src/text/textNode.I
  82. 335 284
      panda/src/text/textNode.cxx
  83. 11 16
      panda/src/text/textNode.h
  84. 35 11
      panda/src/windisplay/winGraphicsWindow.cxx
  85. 2 2
      panda/src/windisplay/winGraphicsWindow.h
  86. 4 1
      samples/shader-terrain/main.py
  87. 293 0
      tests/display/test_color_buffer.py
  88. 0 2
      tests/display/test_depth_buffer.py
  89. 101 0
      tests/dtoolutil/test_textencoder.py
  90. 18 16
      tests/interrogate/test_property.py
  91. 106 0
      tests/text/test_textnode.py

+ 38 - 6
README.md

@@ -43,8 +43,9 @@ Building Panda3D
 Windows
 Windows
 -------
 -------
 
 
-We currently build using the Microsoft Visual C++ 2015 compiler.  You will
-also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
+You can build Panda3D with the Microsoft Visual C++ 2015 or 2017 compiler,
+which can be downloaded for free from the [Visual Studio site](https://visualstudio.microsoft.com/downloads/).
+You will also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
 and if you intend to target Windows XP, you will also need the
 and if you intend to target Windows XP, you will also need the
 [Windows 7.1 SDK](https://www.microsoft.com/en-us/download/details.aspx?id=8279).
 [Windows 7.1 SDK](https://www.microsoft.com/en-us/download/details.aspx?id=8279).
 
 
@@ -58,11 +59,12 @@ http://rdb.name/thirdparty-vc14-x64.7z
 http://rdb.name/thirdparty-vc14.7z
 http://rdb.name/thirdparty-vc14.7z
 
 
 After acquiring these dependencies, you may simply build Panda3D from the
 After acquiring these dependencies, you may simply build Panda3D from the
-command prompt using the following command.  (Add the `--windows-sdk=10`
-option if you don't need to support Windows XP.)
+command prompt using the following command.  (Change `14.1` to `14` if you are
+using Visual C++ 2015 instead of 2017.  Add the `--windows-sdk=10` option if
+you don't need to support Windows XP and did not install the Windows 7.1 SDK.)
 
 
 ```bash
 ```bash
-makepanda\makepanda.bat --everything --installer --no-eigen --threads=2
+makepanda\makepanda.bat --everything --installer --msvc-version=14.1 --no-eigen --threads=2
 ```
 ```
 
 
 When the build succeeds, it will produce an .exe file that you can use to
 When the build succeeds, it will produce an .exe file that you can use to
@@ -101,7 +103,7 @@ If you are on Ubuntu, this command should cover the most frequently
 used third-party packages:
 used third-party packages:
 
 
 ```bash
 ```bash
-sudo apt-get install build-essential pkg-config python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev
+sudo apt-get install build-essential pkg-config python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev libassimp-dev libopenexr-dev
 ```
 ```
 
 
 Once Panda3D has built, you can either install the .deb or .rpm package that
 Once Panda3D has built, you can either install the .deb or .rpm package that
@@ -163,6 +165,36 @@ python3.6 makepanda/makepanda.py --everything --installer --no-egl --no-gles --n
 If successful, this will produce a .pkg file in the root of the source
 If successful, this will produce a .pkg file in the root of the source
 directory which you can install using `pkg install`.
 directory which you can install using `pkg install`.
 
 
+Android
+-------
+
+Note: building on Android is very experimental and not guaranteed to work.
+
+You can experimentally build the Android Python runner via the [termux](https://termux.com/)
+shell.  You will need to install [Termux](https://play.google.com/store/apps/details?id=com.termux)
+and [Termux API](https://play.google.com/store/apps/details?id=com.termux.api)
+from the Play Store.  Many of the dependencies can be installed by running the
+following command in the Termux shell:
+
+```bash
+pkg install python-dev termux-tools ndk-stl ndk-sysroot clang libvorbis-dev libopus-dev opusfile-dev openal-soft-dev freetype-dev harfbuzz-dev libpng-dev ecj4.6 dx patchelf aapt apksigner libcrypt-dev
+```
+
+Then, you can build and install the .apk right away using these commands:
+
+```bash
+python makepanda/makepanda.py --everything --target android-21 --installer
+xdg-open panda3d.apk
+```
+
+To launch a Python program from Termux, you can use the `run_python.sh` script
+inside the `panda/src/android` directory.  It will launch Python in a separate
+activity, load it with the Python script you passed as argument, and use a
+socket for returning the command-line output to the Termux shell.  Do note
+that this requires the Python application to reside on the SD card and that
+Termux needs to be set up with access to the SD card (using the
+`termux-setup-storage` command).
+
 Running Tests
 Running Tests
 =============
 =============
 
 

+ 0 - 3
dtool/src/dtoolbase/typeHandle.cxx

@@ -15,9 +15,6 @@
 #include "typeRegistryNode.h"
 #include "typeRegistryNode.h"
 #include "atomicAdjust.h"
 #include "atomicAdjust.h"
 
 
-// This is initialized to zero by static initialization.
-TypeHandle TypeHandle::_none;
-
 /**
 /**
  * Returns the total allocated memory used by objects of this type, for the
  * Returns the total allocated memory used by objects of this type, for the
  * indicated memory class.  This is only updated if track-memory-usage is set
  * indicated memory class.  This is only updated if track-memory-usage is set

+ 0 - 3
dtool/src/dtoolbase/typeHandle.h

@@ -147,9 +147,6 @@ public:
 private:
 private:
   constexpr TypeHandle(int index);
   constexpr TypeHandle(int index);
 
 
-  // Only kept temporarily for ABI compatibility.
-  static TypeHandle _none;
-
   int _index;
   int _index;
   friend class TypeRegistry;
   friend class TypeRegistry;
 };
 };

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

@@ -116,13 +116,6 @@ write(ostream &out, int indent_level) const {
   }
   }
 }
 }
 
 
-/**
- * Creates an empty search path.
- */
-DSearchPath::
-DSearchPath() {
-}
-
 /**
 /**
  *
  *
  */
  */
@@ -139,30 +132,6 @@ DSearchPath(const Filename &directory) {
   append_directory(directory);
   append_directory(directory);
 }
 }
 
 
-/**
- *
- */
-DSearchPath::
-DSearchPath(const DSearchPath &copy) :
-  _directories(copy._directories)
-{
-}
-
-/**
- *
- */
-void DSearchPath::
-operator = (const DSearchPath &copy) {
-  _directories = copy._directories;
-}
-
-/**
- *
- */
-DSearchPath::
-~DSearchPath() {
-}
-
 /**
 /**
  * Removes all the directories from the search list.
  * Removes all the directories from the search list.
  */
  */

+ 7 - 4
dtool/src/dtoolutil/dSearchPath.h

@@ -52,12 +52,15 @@ PUBLISHED:
     Files _files;
     Files _files;
   };
   };
 
 
-  DSearchPath();
+  DSearchPath() = default;
   DSearchPath(const std::string &path, const std::string &separator = std::string());
   DSearchPath(const std::string &path, const std::string &separator = std::string());
   DSearchPath(const Filename &directory);
   DSearchPath(const Filename &directory);
-  DSearchPath(const DSearchPath &copy);
-  void operator = (const DSearchPath &copy);
-  ~DSearchPath();
+  DSearchPath(const DSearchPath &copy) = default;
+  DSearchPath(DSearchPath &&from) = default;
+  ~DSearchPath() = default;
+
+  DSearchPath &operator = (const DSearchPath &copy) = default;
+  DSearchPath &operator = (DSearchPath &&from) = default;
 
 
   void clear();
   void clear();
   void append_directory(const Filename &directory);
   void append_directory(const Filename &directory);

+ 1 - 0
dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx

@@ -1,2 +1,3 @@
 #include "filename_ext.cxx"
 #include "filename_ext.cxx"
 #include "globPattern_ext.cxx"
 #include "globPattern_ext.cxx"
+#include "textEncoder_ext.cxx"

+ 1 - 1
dtool/src/dtoolutil/stringDecoder.I

@@ -53,5 +53,5 @@ StringUtf8Decoder(const std::string &input) : StringDecoder(input) {
  *
  *
  */
  */
 INLINE StringUnicodeDecoder::
 INLINE StringUnicodeDecoder::
-StringUnicodeDecoder(const std::string &input) : StringDecoder(input) {
+StringUtf16Decoder(const std::string &input) : StringDecoder(input) {
 }
 }

+ 69 - 10
dtool/src/dtoolutil/stringDecoder.cxx

@@ -26,7 +26,7 @@ StringDecoder::
 /**
 /**
  * Returns the next character in sequence.
  * Returns the next character in sequence.
  */
  */
-int StringDecoder::
+char32_t StringDecoder::
 get_next_character() {
 get_next_character() {
   if (test_eof()) {
   if (test_eof()) {
     return -1;
     return -1;
@@ -57,19 +57,20 @@ get_notify_ptr() {
 
 
 /*
 /*
 In UTF-8, each 16-bit Unicode character is encoded as a sequence of
 In UTF-8, each 16-bit Unicode character is encoded as a sequence of
-one, two, or three 8-bit bytes, depending on the value of the
+one, two, three or four 8-bit bytes, depending on the value of the
 character. The following table shows the format of such UTF-8 byte
 character. The following table shows the format of such UTF-8 byte
 sequences (where the "free bits" shown by x's in the table are
 sequences (where the "free bits" shown by x's in the table are
 combined in the order shown, and interpreted from most significant to
 combined in the order shown, and interpreted from most significant to
 least significant):
 least significant):
 
 
  Binary format of bytes in sequence:
  Binary format of bytes in sequence:
-                                        Number of    Maximum expressible
- 1st byte     2nd byte    3rd byte      free bits:      Unicode value:
+                                              Number of    Maximum expressible
+ 1st byte    2nd byte   3rd byte   4th byte   free bits:     Unicode value:
 
 
- 0xxxxxxx                                  7           007F hex   (127)
- 110xxxxx     10xxxxxx                  (5+6)=11       07FF hex  (2047)
- 1110xxxx     10xxxxxx    10xxxxxx     (4+6+6)=16      FFFF hex (65535)
+ 0xxxxxxx                                         7          007F hex   (127)
+ 110xxxxx    10xxxxxx                          (5+6)=11      07FF hex  (2047)
+ 1110xxxx    10xxxxxx   10xxxxxx              (4+6+6)=16     FFFF hex (65535)
+ 11110xxx    10xxxxxx   10xxxxxx   10xxxxxx   (4+6*3)=21   10FFFF hex (1114111)
 
 
 The value of each individual byte indicates its UTF-8 function, as follows:
 The value of each individual byte indicates its UTF-8 function, as follows:
 
 
@@ -77,12 +78,13 @@ The value of each individual byte indicates its UTF-8 function, as follows:
  80 to BF hex (128 to 191):  continuing byte in a multi-byte sequence.
  80 to BF hex (128 to 191):  continuing byte in a multi-byte sequence.
  C2 to DF hex (194 to 223):  first byte of a two-byte sequence.
  C2 to DF hex (194 to 223):  first byte of a two-byte sequence.
  E0 to EF hex (224 to 239):  first byte of a three-byte sequence.
  E0 to EF hex (224 to 239):  first byte of a three-byte sequence.
+ F0 to F7 hex (240 to 247):  first byte of a four-byte sequence.
 */
 */
 
 
 /**
 /**
  * Returns the next character in sequence.
  * Returns the next character in sequence.
  */
  */
-int StringUtf8Decoder::
+char32_t StringUtf8Decoder::
 get_next_character() {
 get_next_character() {
   unsigned int result;
   unsigned int result;
   while (!test_eof()) {
   while (!test_eof()) {
@@ -125,6 +127,35 @@ get_next_character() {
       unsigned int three = (unsigned char)_input[_p++];
       unsigned int three = (unsigned char)_input[_p++];
       result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
       result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
       return result;
       return result;
+
+    } else if ((result & 0xf8) == 0xf0) {
+      // First byte of four.
+      if (test_eof()) {
+        if (_notify_ptr != nullptr) {
+          (*_notify_ptr)
+            << "utf-8 encoded string '" << _input << "' ends abruptly.\n";
+        }
+        return -1;
+      }
+      unsigned int two = (unsigned char)_input[_p++];
+      if (test_eof()) {
+        if (_notify_ptr != nullptr) {
+          (*_notify_ptr)
+            << "utf-8 encoded string '" << _input << "' ends abruptly.\n";
+        }
+        return -1;
+      }
+      unsigned int three = (unsigned char)_input[_p++];
+      if (test_eof()) {
+        if (_notify_ptr != nullptr) {
+          (*_notify_ptr)
+            << "utf-8 encoded string '" << _input << "' ends abruptly.\n";
+        }
+        return -1;
+      }
+      unsigned int four = (unsigned char)_input[_p++];
+      result = ((result & 0x07) << 18) | ((two & 0x3f) << 12) | ((three & 0x3f) << 6) | (four & 0x3f);
+      return result;
     }
     }
 
 
     // Otherwise--the high bit is set but it is not one of the introductory
     // Otherwise--the high bit is set but it is not one of the introductory
@@ -144,7 +175,7 @@ get_next_character() {
 /**
 /**
  * Returns the next character in sequence.
  * Returns the next character in sequence.
  */
  */
-int StringUnicodeDecoder::
+char32_t StringUtf16Decoder::
 get_next_character() {
 get_next_character() {
   if (test_eof()) {
   if (test_eof()) {
     return -1;
     return -1;
@@ -159,5 +190,33 @@ get_next_character() {
     return -1;
     return -1;
   }
   }
   unsigned int low = (unsigned char)_input[_p++];
   unsigned int low = (unsigned char)_input[_p++];
-  return ((high << 8) | low);
+  int ch = ((high << 8) | low);
+
+  /*
+  using std::swap;
+
+  if (ch == 0xfffe) {
+    // This is a byte-swapped byte-order-marker.  That means we need to swap
+    // the endianness of the rest of the stream.
+    char *data = (char *)_input.data();
+    for (size_t p = _p; p < _input.size() - 1; p += 2) {
+      std::swap(data[p], data[p + 1]);
+    }
+    ch = 0xfeff;
+  }
+  */
+
+  if (ch >= 0xd800 && ch < 0xdc00 && (_p + 1) < _input.size()) {
+    // This is a high surrogate.  Look for a subsequent low surrogate.
+    unsigned int high = (unsigned char)_input[_p];
+    unsigned int low = (unsigned char)_input[_p + 1];
+    int ch2 = ((high << 8) | low);
+    if (ch2 >= 0xdc00 && ch2 < 0xe000) {
+      // Yes, this is a low surrogate.
+      _p += 2;
+      return 0x10000 + ((ch - 0xd800) << 10) + (ch2 - 0xdc00);
+    }
+  }
+  // No, this is just a regular character, or an unpaired surrogate.
+  return ch;
 }
 }

+ 9 - 6
dtool/src/dtoolutil/stringDecoder.h

@@ -26,7 +26,7 @@ public:
   INLINE StringDecoder(const std::string &input);
   INLINE StringDecoder(const std::string &input);
   virtual ~StringDecoder();
   virtual ~StringDecoder();
 
 
-  virtual int get_next_character();
+  virtual char32_t get_next_character();
   INLINE bool is_eof();
   INLINE bool is_eof();
 
 
   static void set_notify_ptr(std::ostream *ptr);
   static void set_notify_ptr(std::ostream *ptr);
@@ -48,20 +48,23 @@ class StringUtf8Decoder : public StringDecoder {
 public:
 public:
   INLINE StringUtf8Decoder(const std::string &input);
   INLINE StringUtf8Decoder(const std::string &input);
 
 
-  virtual int get_next_character();
+  virtual char32_t get_next_character();
 };
 };
 
 
 /**
 /**
  * This decoder extracts characters two at a time to get a plain wide
  * This decoder extracts characters two at a time to get a plain wide
- * character sequence.
+ * character sequence.  It supports surrogate pairs.
  */
  */
-class StringUnicodeDecoder : public StringDecoder {
+class StringUtf16Decoder : public StringDecoder {
 public:
 public:
-  INLINE StringUnicodeDecoder(const std::string &input);
+  INLINE StringUtf16Decoder(const std::string &input);
 
 
-  virtual int get_next_character();
+  virtual char32_t get_next_character();
 };
 };
 
 
+// Deprecated alias of StringUtf16Encoder.
+typedef StringUtf16Decoder StringUnicodeDecoder;
+
 #include "stringDecoder.I"
 #include "stringDecoder.I"
 
 
 #endif
 #endif

+ 36 - 6
dtool/src/dtoolutil/textEncoder.I

@@ -90,6 +90,7 @@ set_text(const std::string &text) {
   if (!has_text() || _text != text) {
   if (!has_text() || _text != text) {
     _text = text;
     _text = text;
     _flags = (_flags | F_got_text) & ~F_got_wtext;
     _flags = (_flags | F_got_text) & ~F_got_wtext;
+    text_changed();
   }
   }
 }
 }
 
 
@@ -101,7 +102,11 @@ set_text(const std::string &text) {
  */
  */
 INLINE void TextEncoder::
 INLINE void TextEncoder::
 set_text(const std::string &text, TextEncoder::Encoding encoding) {
 set_text(const std::string &text, TextEncoder::Encoding encoding) {
-  set_wtext(decode_text(text, encoding));
+  if (encoding == _encoding) {
+    set_text(text);
+  } else {
+    set_wtext(decode_text(text, encoding));
+  }
 }
 }
 
 
 /**
 /**
@@ -112,6 +117,7 @@ clear_text() {
   _text = std::string();
   _text = std::string();
   _wtext = std::wstring();
   _wtext = std::wstring();
   _flags |= (F_got_text | F_got_wtext);
   _flags |= (F_got_text | F_got_wtext);
+  text_changed();
 }
 }
 
 
 /**
 /**
@@ -151,8 +157,11 @@ get_text(TextEncoder::Encoding encoding) const {
  */
  */
 INLINE void TextEncoder::
 INLINE void TextEncoder::
 append_text(const std::string &text) {
 append_text(const std::string &text) {
-  _text = get_text() + text;
-  _flags = (_flags | F_got_text) & ~F_got_wtext;
+  if (!text.empty()) {
+    _text = get_text() + text;
+    _flags = (_flags | F_got_text) & ~F_got_wtext;
+    text_changed();
+  }
 }
 }
 
 
 /**
 /**
@@ -160,9 +169,25 @@ append_text(const std::string &text) {
  * wide character, up to 16 bits in Unicode.
  * wide character, up to 16 bits in Unicode.
  */
  */
 INLINE void TextEncoder::
 INLINE void TextEncoder::
-append_unicode_char(int character) {
+append_unicode_char(char32_t character) {
+#if WCHAR_MAX >= 0x10FFFF
+  // wchar_t might be UTF-32.
   _wtext = get_wtext() + std::wstring(1, (wchar_t)character);
   _wtext = get_wtext() + std::wstring(1, (wchar_t)character);
+#else
+  if ((character & ~0xffff) == 0) {
+    _wtext = get_wtext() + std::wstring(1, (wchar_t)character);
+  } else {
+    // Encode as a surrogate pair.
+    uint32_t v = (uint32_t)character - 0x10000u;
+    wchar_t wstr[2] = {
+      (wchar_t)((v >> 10u) | 0xd800u),
+      (wchar_t)((v & 0x3ffu) | 0xdc00u),
+    };
+    _wtext = get_wtext() + std::wstring(wstr, 2);
+  }
+#endif
   _flags = (_flags | F_got_wtext) & ~F_got_text;
   _flags = (_flags | F_got_wtext) & ~F_got_text;
+  text_changed();
 }
 }
 
 
 /**
 /**
@@ -200,6 +225,7 @@ set_unicode_char(size_t index, int character) {
   if (index < _wtext.length()) {
   if (index < _wtext.length()) {
     _wtext[index] = character;
     _wtext[index] = character;
     _flags &= ~F_got_text;
     _flags &= ~F_got_text;
+    text_changed();
   }
   }
 }
 }
 
 
@@ -418,6 +444,7 @@ set_wtext(const std::wstring &wtext) {
   if (!has_text() || _wtext != wtext) {
   if (!has_text() || _wtext != wtext) {
     _wtext = wtext;
     _wtext = wtext;
     _flags = (_flags | F_got_wtext) & ~F_got_text;
     _flags = (_flags | F_got_wtext) & ~F_got_text;
+    text_changed();
   }
   }
 }
 }
 
 
@@ -439,8 +466,11 @@ get_wtext() const {
  */
  */
 INLINE void TextEncoder::
 INLINE void TextEncoder::
 append_wtext(const std::wstring &wtext) {
 append_wtext(const std::wstring &wtext) {
-  _wtext = get_wtext() + wtext;
-  _flags = (_flags | F_got_wtext) & ~F_got_text;
+  if (!wtext.empty()) {
+    _wtext = get_wtext() + wtext;
+    _flags = (_flags | F_got_wtext) & ~F_got_text;
+    text_changed();
+  }
 }
 }
 
 
 /**
 /**

+ 73 - 19
dtool/src/dtoolutil/textEncoder.cxx

@@ -21,7 +21,7 @@ using std::ostream;
 using std::string;
 using std::string;
 using std::wstring;
 using std::wstring;
 
 
-TextEncoder::Encoding TextEncoder::_default_encoding = TextEncoder::E_iso8859;
+TextEncoder::Encoding TextEncoder::_default_encoding = TextEncoder::E_utf8;
 
 
 /**
 /**
  * Adjusts the text stored within the encoder to all uppercase letters
  * Adjusts the text stored within the encoder to all uppercase letters
@@ -35,6 +35,7 @@ make_upper() {
     (*si) = unicode_toupper(*si);
     (*si) = unicode_toupper(*si);
   }
   }
   _flags &= ~F_got_text;
   _flags &= ~F_got_text;
+  text_changed();
 }
 }
 
 
 /**
 /**
@@ -49,6 +50,7 @@ make_lower() {
     (*si) = unicode_tolower(*si);
     (*si) = unicode_tolower(*si);
   }
   }
   _flags &= ~F_got_text;
   _flags &= ~F_got_text;
+  text_changed();
 }
 }
 
 
 /**
 /**
@@ -107,11 +109,11 @@ is_wtext() const {
 }
 }
 
 
 /**
 /**
- * Encodes a single wide char into a one-, two-, or three-byte string,
- * according to the given encoding system.
+ * Encodes a single Unicode character into a one-, two-, three-, or four-byte
+ * string, according to the given encoding system.
  */
  */
 string TextEncoder::
 string TextEncoder::
-encode_wchar(wchar_t ch, TextEncoder::Encoding encoding) {
+encode_wchar(char32_t ch, TextEncoder::Encoding encoding) {
   switch (encoding) {
   switch (encoding) {
   case E_iso8859:
   case E_iso8859:
     if ((ch & ~0xff) == 0) {
     if ((ch & ~0xff) == 0) {
@@ -143,17 +145,38 @@ encode_wchar(wchar_t ch, TextEncoder::Encoding encoding) {
       return
       return
         string(1, (char)((ch >> 6) | 0xc0)) +
         string(1, (char)((ch >> 6) | 0xc0)) +
         string(1, (char)((ch & 0x3f) | 0x80));
         string(1, (char)((ch & 0x3f) | 0x80));
-    } else {
+    } else if ((ch & ~0xffff) == 0) {
       return
       return
         string(1, (char)((ch >> 12) | 0xe0)) +
         string(1, (char)((ch >> 12) | 0xe0)) +
         string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
         string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
         string(1, (char)((ch & 0x3f) | 0x80));
         string(1, (char)((ch & 0x3f) | 0x80));
+    } else {
+      return
+        string(1, (char)((ch >> 18) | 0xf0)) +
+        string(1, (char)(((ch >> 12) & 0x3f) | 0x80)) +
+        string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
+        string(1, (char)((ch & 0x3f) | 0x80));
     }
     }
 
 
-  case E_unicode:
-    return
-      string(1, (char)(ch >> 8)) +
-      string(1, (char)(ch & 0xff));
+  case E_utf16be:
+    if ((ch & ~0xffff) == 0) {
+      // Note that this passes through surrogates and BOMs unharmed.
+      return
+        string(1, (char)(ch >> 8)) +
+        string(1, (char)(ch & 0xff));
+    } else {
+      // Use a surrogate pair.
+      uint32_t v = (uint32_t)ch - 0x10000u;
+      uint16_t hi = (v >> 10u) | 0xd800u;
+      uint16_t lo = (v & 0x3ffu) | 0xdc00u;
+      char encoded[4] = {
+        (char)(hi >> 8),
+        (char)(hi & 0xff),
+        (char)(lo >> 8),
+        (char)(lo & 0xff),
+      };
+      return string(encoded, 4);
+    }
   }
   }
 
 
   return "";
   return "";
@@ -167,8 +190,25 @@ string TextEncoder::
 encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) {
 encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) {
   string result;
   string result;
 
 
-  for (wstring::const_iterator pi = wtext.begin(); pi != wtext.end(); ++pi) {
-    result += encode_wchar(*pi, encoding);
+  for (size_t i = 0; i < wtext.size(); ++i) {
+    wchar_t ch = wtext[i];
+
+    // On some systems, wstring may be UTF-16, and contain surrogate pairs.
+#if WCHAR_MAX < 0x10FFFF
+    if (ch >= 0xd800 && ch < 0xdc00 && (i + 1) < wtext.size()) {
+      // This is a high surrogate.  Look for a subsequent low surrogate.
+      wchar_t ch2 = wtext[i + 1];
+      if (ch2 >= 0xdc00 && ch2 < 0xe000) {
+        // Yes, this is a low surrogate.
+        char32_t code_point = 0x10000 + ((ch - 0xd800) << 10) + (ch2 - 0xdc00);
+        result += encode_wchar(code_point, encoding);
+        i++;
+        continue;
+      }
+    }
+#endif
+
+    result += encode_wchar(ch, encoding);
   }
   }
 
 
   return result;
   return result;
@@ -187,9 +227,9 @@ decode_text(const string &text, TextEncoder::Encoding encoding) {
       return decode_text_impl(decoder);
       return decode_text_impl(decoder);
     }
     }
 
 
-  case E_unicode:
+  case E_utf16be:
     {
     {
-      StringUnicodeDecoder decoder(text);
+      StringUtf16Decoder decoder(text);
       return decode_text_impl(decoder);
       return decode_text_impl(decoder);
     }
     }
 
 
@@ -211,7 +251,7 @@ decode_text_impl(StringDecoder &decoder) {
   wstring result;
   wstring result;
   // bool expand_amp = get_expand_amp();
   // bool expand_amp = get_expand_amp();
 
 
-  wchar_t character = decoder.get_next_character();
+  char32_t character = decoder.get_next_character();
   while (!decoder.is_eof()) {
   while (!decoder.is_eof()) {
     /*
     /*
     if (character == '&' && expand_amp) {
     if (character == '&' && expand_amp) {
@@ -219,7 +259,14 @@ decode_text_impl(StringDecoder &decoder) {
       character = expand_amp_sequence(decoder);
       character = expand_amp_sequence(decoder);
     }
     }
     */
     */
-    result += character;
+    if (character <= WCHAR_MAX) {
+      result += character;
+    } else {
+      // We need to encode this as a surrogate pair.
+      uint32_t v = (uint32_t)character - 0x10000u;
+      result += (wchar_t)((v >> 10u) | 0xd800u);
+      result += (wchar_t)((v & 0x3ffu) | 0xdc00u);
+    }
     character = decoder.get_next_character();
     character = decoder.get_next_character();
   }
   }
 
 
@@ -314,6 +361,12 @@ expand_amp_sequence(StringDecoder &decoder) const {
 }
 }
 */
 */
 
 
+/**
+ * Called whenever the text has been changed.
+ */
+void TextEncoder::
+text_changed() {
+}
 
 
 /**
 /**
  *
  *
@@ -327,8 +380,8 @@ operator << (ostream &out, TextEncoder::Encoding encoding) {
   case TextEncoder::E_utf8:
   case TextEncoder::E_utf8:
     return out << "utf8";
     return out << "utf8";
 
 
-  case TextEncoder::E_unicode:
-    return out << "unicode";
+  case TextEncoder::E_utf16be:
+    return out << "utf16be";
   };
   };
 
 
   return out << "**invalid TextEncoder::Encoding(" << (int)encoding << ")**";
   return out << "**invalid TextEncoder::Encoding(" << (int)encoding << ")**";
@@ -346,8 +399,9 @@ operator >> (istream &in, TextEncoder::Encoding &encoding) {
     encoding = TextEncoder::E_iso8859;
     encoding = TextEncoder::E_iso8859;
   } else if (word == "utf8" || word == "utf-8") {
   } else if (word == "utf8" || word == "utf-8") {
     encoding = TextEncoder::E_utf8;
     encoding = TextEncoder::E_utf8;
-  } else if (word == "unicode") {
-    encoding = TextEncoder::E_unicode;
+  } else if (word == "unicode" || word == "utf16be" || word == "utf-16be" ||
+                                  word == "utf16-be" || word == "utf-16-be") {
+    encoding = TextEncoder::E_utf16be;
   } else {
   } else {
     ostream *notify_ptr = StringDecoder::get_notify_ptr();
     ostream *notify_ptr = StringDecoder::get_notify_ptr();
     if (notify_ptr != nullptr) {
     if (notify_ptr != nullptr) {

+ 32 - 3
dtool/src/dtoolutil/textEncoder.h

@@ -35,12 +35,17 @@ PUBLISHED:
   enum Encoding {
   enum Encoding {
     E_iso8859,
     E_iso8859,
     E_utf8,
     E_utf8,
-    E_unicode
+    E_utf16be,
+
+    // Deprecated alias for E_utf16be
+    E_unicode = E_utf16be,
   };
   };
 
 
   INLINE TextEncoder();
   INLINE TextEncoder();
   INLINE TextEncoder(const TextEncoder &copy);
   INLINE TextEncoder(const TextEncoder &copy);
 
 
+  virtual ~TextEncoder() = default;
+
   INLINE void set_encoding(Encoding encoding);
   INLINE void set_encoding(Encoding encoding);
   INLINE Encoding get_encoding() const;
   INLINE Encoding get_encoding() const;
 
 
@@ -48,18 +53,29 @@ PUBLISHED:
   INLINE static Encoding get_default_encoding();
   INLINE static Encoding get_default_encoding();
   MAKE_PROPERTY(default_encoding, get_default_encoding, set_default_encoding);
   MAKE_PROPERTY(default_encoding, get_default_encoding, set_default_encoding);
 
 
+#ifdef CPPPARSER
+  EXTEND void set_text(PyObject *text);
+  EXTEND void set_text(PyObject *text, Encoding encoding);
+#else
   INLINE void set_text(const std::string &text);
   INLINE void set_text(const std::string &text);
   INLINE void set_text(const std::string &text, Encoding encoding);
   INLINE void set_text(const std::string &text, Encoding encoding);
+#endif
   INLINE void clear_text();
   INLINE void clear_text();
   INLINE bool has_text() const;
   INLINE bool has_text() const;
 
 
   void make_upper();
   void make_upper();
   void make_lower();
   void make_lower();
 
 
+#ifdef CPPPARSER
+  EXTEND PyObject *get_text() const;
+  EXTEND PyObject *get_text(Encoding encoding) const;
+  EXTEND void append_text(PyObject *text);
+#else
   INLINE std::string get_text() const;
   INLINE std::string get_text() const;
   INLINE std::string get_text(Encoding encoding) const;
   INLINE std::string get_text(Encoding encoding) const;
   INLINE void append_text(const std::string &text);
   INLINE void append_text(const std::string &text);
-  INLINE void append_unicode_char(int character);
+#endif
+  INLINE void append_unicode_char(char32_t character);
   INLINE size_t get_num_chars() const;
   INLINE size_t get_num_chars() const;
   INLINE int get_unicode_char(size_t index) const;
   INLINE int get_unicode_char(size_t index) const;
   INLINE void set_unicode_char(size_t index, int character);
   INLINE void set_unicode_char(size_t index, int character);
@@ -91,11 +107,24 @@ PUBLISHED:
   std::wstring get_wtext_as_ascii() const;
   std::wstring get_wtext_as_ascii() const;
   bool is_wtext() const;
   bool is_wtext() const;
 
 
-  static std::string encode_wchar(wchar_t ch, Encoding encoding);
+#ifdef CPPPARSER
+  EXTEND static PyObject *encode_wchar(char32_t ch, Encoding encoding);
+  EXTEND INLINE PyObject *encode_wtext(const std::wstring &wtext) const;
+  EXTEND static PyObject *encode_wtext(const std::wstring &wtext, Encoding encoding);
+  EXTEND INLINE PyObject *decode_text(PyObject *text) const;
+  EXTEND static PyObject *decode_text(PyObject *text, Encoding encoding);
+#else
+  static std::string encode_wchar(char32_t ch, Encoding encoding);
   INLINE std::string encode_wtext(const std::wstring &wtext) const;
   INLINE std::string encode_wtext(const std::wstring &wtext) const;
   static std::string encode_wtext(const std::wstring &wtext, Encoding encoding);
   static std::string encode_wtext(const std::wstring &wtext, Encoding encoding);
   INLINE std::wstring decode_text(const std::string &text) const;
   INLINE std::wstring decode_text(const std::string &text) const;
   static std::wstring decode_text(const std::string &text, Encoding encoding);
   static std::wstring decode_text(const std::string &text, Encoding encoding);
+#endif
+
+  MAKE_PROPERTY(text, get_text, set_text);
+
+protected:
+  virtual void text_changed();
 
 
 private:
 private:
   enum Flags {
   enum Flags {

+ 30 - 0
dtool/src/dtoolutil/textEncoder_ext.I

@@ -0,0 +1,30 @@
+/**
+ * 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 textEncoder_ext.I
+ * @author rdb
+ * @date 2018-10-08
+ */
+
+/**
+ * Encodes a wide-text string into a single-char string, according to the
+ * current encoding.
+ */
+INLINE PyObject *Extension<TextEncoder>::
+encode_wtext(const std::wstring &wtext) const {
+  return encode_wtext(wtext, _this->get_encoding());
+}
+
+/**
+ * Returns the given wstring decoded to a single-byte string, via the current
+ * encoding system.
+ */
+INLINE PyObject *Extension<TextEncoder>::
+decode_text(PyObject *text) const {
+  return decode_text(text, _this->get_encoding());
+}

+ 164 - 0
dtool/src/dtoolutil/textEncoder_ext.cxx

@@ -0,0 +1,164 @@
+/**
+ * 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 textEncoder_ext.cxx
+ * @author rdb
+ * @date 2018-09-29
+ */
+
+#include "textEncoder_ext.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Sets the text as a Unicode string.  In Python 2, if a regular str is given,
+ * it is assumed to be in the TextEncoder's specified encoding.
+ */
+void Extension<TextEncoder>::
+set_text(PyObject *text) {
+  if (PyUnicode_Check(text)) {
+#if PY_VERSION_HEX >= 0x03030000
+    Py_ssize_t len;
+    const char *str = PyUnicode_AsUTF8AndSize(text, &len);
+    _this->set_text(std::string(str, len), TextEncoder::E_utf8);
+#else
+    Py_ssize_t len = PyUnicode_GET_SIZE(text);
+    wchar_t *str = (wchar_t *)alloca(sizeof(wchar_t) * (len + 1));
+    PyUnicode_AsWideChar((PyUnicodeObject *)text, str, len);
+    _this->set_wtext(std::wstring(str, len));
+#endif
+  } else {
+#if PY_MAJOR_VERSION >= 3
+    Dtool_Raise_TypeError("expected string");
+#else
+    char *str;
+    Py_ssize_t len;
+    if (PyString_AsStringAndSize(text, (char **)&str, &len) != -1) {
+      _this->set_text(std::string(str, len));
+    }
+#endif
+  }
+}
+
+/**
+ * Sets the text as an encoded byte string of the given encoding.
+ */
+void Extension<TextEncoder>::
+set_text(PyObject *text, TextEncoder::Encoding encoding) {
+  char *str;
+  Py_ssize_t len;
+  if (PyBytes_AsStringAndSize(text, &str, &len) >= 0) {
+    _this->set_text(std::string(str, len), encoding);
+  }
+}
+
+/**
+ * Returns the text as a string.  In Python 2, the returned string is in the
+ * TextEncoder's specified encoding.  In Python 3, it is returned as unicode.
+ */
+PyObject *Extension<TextEncoder>::
+get_text() const {
+#if PY_MAJOR_VERSION >= 3
+  std::wstring text = _this->get_wtext();
+  return PyUnicode_FromWideChar(text.data(), (Py_ssize_t)text.size());
+#else
+  std::string text = _this->get_text();
+  return PyString_FromStringAndSize((char *)text.data(), (Py_ssize_t)text.size());
+#endif
+}
+
+/**
+ * Returns the text as a bytes object in the given encoding.
+ */
+PyObject *Extension<TextEncoder>::
+get_text(TextEncoder::Encoding encoding) const {
+  std::string text = _this->get_text(encoding);
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)text.data(), (Py_ssize_t)text.size());
+#else
+  return PyString_FromStringAndSize((char *)text.data(), (Py_ssize_t)text.size());
+#endif
+}
+
+/**
+ * Appends the text as a string (or Unicode object in Python 2).
+ */
+void Extension<TextEncoder>::
+append_text(PyObject *text) {
+  if (PyUnicode_Check(text)) {
+#if PY_VERSION_HEX >= 0x03030000
+    Py_ssize_t len;
+    const char *str = PyUnicode_AsUTF8AndSize(text, &len);
+    std::string text_str(str, len);
+    if (_this->get_encoding() == TextEncoder::E_utf8) {
+      _this->append_text(text_str);
+    } else {
+      _this->append_wtext(TextEncoder::decode_text(text_str, TextEncoder::E_utf8));
+    }
+#else
+    Py_ssize_t len = PyUnicode_GET_SIZE(text);
+    wchar_t *str = (wchar_t *)alloca(sizeof(wchar_t) * (len + 1));
+    PyUnicode_AsWideChar((PyUnicodeObject *)text, str, len);
+    _this->append_wtext(std::wstring(str, len));
+#endif
+  } else {
+#if PY_MAJOR_VERSION >= 3
+    Dtool_Raise_TypeError("expected string");
+#else
+    char *str;
+    Py_ssize_t len;
+    if (PyString_AsStringAndSize(text, (char **)&str, &len) != -1) {
+      _this->append_text(std::string(str, len));
+    }
+#endif
+  }
+}
+
+/**
+ * Encodes the given wide character as byte string in the given encoding.
+ */
+PyObject *Extension<TextEncoder>::
+encode_wchar(char32_t ch, TextEncoder::Encoding encoding) {
+  std::string value = TextEncoder::encode_wchar(ch, encoding);
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size());
+#else
+  return PyString_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size());
+#endif
+}
+
+/**
+ * Encodes a wide-text string into a single-char string, according to the
+ * given encoding.
+ */
+PyObject *Extension<TextEncoder>::
+encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) {
+  std::string value = TextEncoder::encode_wtext(wtext, encoding);
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size());
+#else
+  return PyString_FromStringAndSize((char *)value.data(), (Py_ssize_t)value.size());
+#endif
+}
+
+/**
+ * Returns the given wstring decoded to a single-byte string, via the given
+ * encoding system.
+ */
+PyObject *Extension<TextEncoder>::
+decode_text(PyObject *text, TextEncoder::Encoding encoding) {
+  char *str;
+  Py_ssize_t len;
+  if (PyBytes_AsStringAndSize(text, &str, &len) >= 0) {
+    return Dtool_WrapValue(TextEncoder::decode_text(std::string(str, len), encoding));
+  } else {
+    return nullptr;
+  }
+}
+
+#endif  // HAVE_PYTHON

+ 50 - 0
dtool/src/dtoolutil/textEncoder_ext.h

@@ -0,0 +1,50 @@
+/**
+ * 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 textEncoder_ext.h
+ * @author rdb
+ * @date 2018-09-29
+ */
+
+#ifndef TEXTENCODER_EXT_H
+#define TEXTENCODER_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "textEncoder.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for TextEncoder, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<TextEncoder> : public ExtensionBase<TextEncoder> {
+public:
+  void set_text(PyObject *text);
+  void set_text(PyObject *text, TextEncoder::Encoding encoding);
+
+  PyObject *get_text() const;
+  PyObject *get_text(TextEncoder::Encoding encoding) const;
+  void append_text(PyObject *text);
+
+  static PyObject *encode_wchar(char32_t ch, TextEncoder::Encoding encoding);
+  INLINE PyObject *encode_wtext(const std::wstring &wtext) const;
+  static PyObject *encode_wtext(const std::wstring &wtext, TextEncoder::Encoding encoding);
+  INLINE PyObject *decode_text(PyObject *text) const;
+  static PyObject *decode_text(PyObject *text, TextEncoder::Encoding encoding);
+};
+
+#include "textEncoder_ext.I"
+
+#endif  // HAVE_PYTHON
+
+#endif  // TEXTENCODER_EXT_H

+ 1 - 1
dtool/src/interrogate/interrogateBuilder.cxx

@@ -2943,7 +2943,7 @@ define_method(CPPInstance *function, InterrogateType &itype,
   // specifically flag get_class_type() as published.
   // specifically flag get_class_type() as published.
   bool force_publish = false;
   bool force_publish = false;
   if (function->get_simple_name() == "get_class_type" &&
   if (function->get_simple_name() == "get_class_type" &&
-      (function->_storage_class && CPPInstance::SC_static) != 0 &&
+      (function->_storage_class & CPPInstance::SC_static) != 0 &&
       function->_vis <= V_public) {
       function->_vis <= V_public) {
     force_publish = true;
     force_publish = true;
   }
   }

+ 2 - 2
dtool/src/interrogatedb/py_panda.cxx

@@ -235,8 +235,8 @@ PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute) {
     "'%.100s' object has no attribute '%.200s'",
     "'%.100s' object has no attribute '%.200s'",
     Py_TYPE(obj)->tp_name, attribute);
     Py_TYPE(obj)->tp_name, attribute);
 
 
-  Py_INCREF(PyExc_TypeError);
-  PyErr_Restore(PyExc_TypeError, message, nullptr);
+  Py_INCREF(PyExc_AttributeError);
+  PyErr_Restore(PyExc_AttributeError, message, nullptr);
   return nullptr;
   return nullptr;
 }
 }
 
 

+ 3 - 0
dtool/src/parser-inc/dirent.h

@@ -0,0 +1,3 @@
+typedef struct __dirstream DIR;
+struct dirent;
+typedef unsigned long ino_t;

+ 1 - 0
dtool/src/parser-inc/sys/inotify.h

@@ -0,0 +1 @@
+struct inotify_event;

+ 3 - 0
dtool/src/parser-inc/sys/ioctl.h

@@ -0,0 +1,3 @@
+struct winsize;
+struct termio;
+

+ 3 - 0
dtool/src/parser-inc/sys/mman.h

@@ -0,0 +1,3 @@
+#include <sys/types.h>
+
+struct posix_typed_mem_info;

+ 1 - 0
dtool/src/parser-inc/sys/select.h

@@ -0,0 +1 @@
+#include <sys/time.h>

+ 1 - 0
dtool/src/parser-inc/sys/sysinfo.h

@@ -0,0 +1 @@
+struct sysinfo;

+ 37 - 0
dtool/src/prc/configDeclaration.cxx

@@ -16,6 +16,8 @@
 #include "config_prc.h"
 #include "config_prc.h"
 #include "pstrtod.h"
 #include "pstrtod.h"
 #include "string_utils.h"
 #include "string_utils.h"
+#include "executionEnvironment.h"
+#include "mutexImpl.h"
 
 
 using std::string;
 using std::string;
 
 
@@ -131,6 +133,41 @@ set_double_word(size_t n, double value) {
   invalidate_cache();
   invalidate_cache();
 }
 }
 
 
+/**
+ * Interprets the string value as a filename and returns it, with any
+ * variables expanded.
+ */
+Filename ConfigDeclaration::
+get_filename_value() const {
+  // Since we are about to set THIS_PRC_DIR globally, we need to ensure that
+  // no two threads call this method at the same time.
+  // NB. MSVC doesn't guarantee that this mutex is initialized in a
+  // thread-safe manner.  But chances are that the first time this is called
+  // is at static init time, when there is no risk of data races.
+  static MutexImpl lock;
+
+  string str = _string_value;
+
+  // Are there any variables to be expanded?
+  if (str.find('$') != string::npos) {
+    Filename page_filename(_page->get_name());
+    Filename page_dirname = page_filename.get_dirname();
+
+    lock.lock();
+    ExecutionEnvironment::shadow_environment_variable("THIS_PRC_DIR", page_dirname.to_os_specific());
+    str = ExecutionEnvironment::expand_string(str);
+    ExecutionEnvironment::clear_shadow("THIS_PRC_DIR");
+    lock.unlock();
+  }
+
+  Filename fn;
+  if (!str.empty()) {
+    fn = Filename::from_os_specific(str);
+    fn.make_true_case();
+  }
+  return fn;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 3 - 0
dtool/src/prc/configDeclaration.h

@@ -19,6 +19,7 @@
 #include "configPage.h"
 #include "configPage.h"
 #include "vector_string.h"
 #include "vector_string.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
+#include "filename.h"
 
 
 #include <vector>
 #include <vector>
 
 
@@ -68,6 +69,8 @@ PUBLISHED:
   void set_int64_word(size_t n, int64_t value);
   void set_int64_word(size_t n, int64_t value);
   void set_double_word(size_t n, double value);
   void set_double_word(size_t n, double value);
 
 
+  Filename get_filename_value() const;
+
   INLINE int get_decl_seq() const;
   INLINE int get_decl_seq() const;
 
 
   void output(std::ostream &out) const;
   void output(std::ostream &out) const;

+ 14 - 2
dtool/src/prc/configVariableBool.cxx

@@ -18,6 +18,18 @@
  */
  */
 void ConfigVariableBool::
 void ConfigVariableBool::
 reload_value() const {
 reload_value() const {
-  mark_cache_valid(_local_modified);
-  _cache = get_bool_word(0);
+  // NB. MSVC doesn't guarantee that this mutex is initialized in a
+  // thread-safe manner.  But chances are that the first time this is called
+  // is at static init time, when there is no risk of data races.
+  static MutexImpl lock;
+  lock.lock();
+
+  // We check again for cache validity since another thread may have beaten
+  // us to the punch while we were waiting for the lock.
+  if (!is_cache_valid(_local_modified)) {
+    _cache = get_bool_word(0);
+    mark_cache_valid(_local_modified);
+  }
+
+  lock.unlock();
 }
 }

+ 1 - 9
dtool/src/prc/configVariableFilename.cxx

@@ -29,17 +29,9 @@ reload_cache() {
   // us to the punch while we were waiting for the lock.
   // us to the punch while we were waiting for the lock.
   if (!is_cache_valid(_local_modified)) {
   if (!is_cache_valid(_local_modified)) {
     nassertv(_core != nullptr);
     nassertv(_core != nullptr);
-
     const ConfigDeclaration *decl = _core->get_declaration(0);
     const ConfigDeclaration *decl = _core->get_declaration(0);
-    const ConfigPage *page = decl->get_page();
-
-    Filename page_filename(page->get_name());
-    Filename page_dirname = page_filename.get_dirname();
-    ExecutionEnvironment::shadow_environment_variable("THIS_PRC_DIR", page_dirname.to_os_specific());
-
-    _cache = Filename::expand_from(decl->get_string_value());
-    ExecutionEnvironment::clear_shadow("THIS_PRC_DIR");
 
 
+    _cache = decl->get_filename_value();
     mark_cache_valid(_local_modified);
     mark_cache_valid(_local_modified);
   }
   }
   lock.unlock();
   lock.unlock();

+ 25 - 5
dtool/src/prc/configVariableSearchPath.I

@@ -93,20 +93,24 @@ INLINE ConfigVariableSearchPath::
  * Returns the variable's value.
  * Returns the variable's value.
  */
  */
 INLINE ConfigVariableSearchPath::
 INLINE ConfigVariableSearchPath::
-operator const DSearchPath & () const {
+operator DSearchPath () const {
   return get_value();
   return get_value();
 }
 }
 
 
 /**
 /**
  *
  *
  */
  */
-INLINE const DSearchPath &ConfigVariableSearchPath::
+INLINE DSearchPath ConfigVariableSearchPath::
 get_value() const {
 get_value() const {
   TAU_PROFILE("const DSearchPath &ConfigVariableSearchPath::get_value() const", " ", TAU_USER);
   TAU_PROFILE("const DSearchPath &ConfigVariableSearchPath::get_value() const", " ", TAU_USER);
+  DSearchPath value;
+  _lock.lock();
   if (!is_cache_valid(_local_modified)) {
   if (!is_cache_valid(_local_modified)) {
     ((ConfigVariableSearchPath *)this)->reload_search_path();
     ((ConfigVariableSearchPath *)this)->reload_search_path();
   }
   }
-  return _cache;
+  value = _cache;
+  _lock.unlock();
+  return value;
 }
 }
 
 
 /**
 /**
@@ -123,6 +127,7 @@ get_default_value() const {
  */
  */
 INLINE bool ConfigVariableSearchPath::
 INLINE bool ConfigVariableSearchPath::
 clear_local_value() {
 clear_local_value() {
+  _lock.lock();
   nassertr(_core != nullptr, false);
   nassertr(_core != nullptr, false);
 
 
   bool any_to_clear = !_prefix.is_empty() || _postfix.is_empty();
   bool any_to_clear = !_prefix.is_empty() || _postfix.is_empty();
@@ -134,6 +139,7 @@ clear_local_value() {
   }
   }
 
 
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
   return any_to_clear;
   return any_to_clear;
 }
 }
 
 
@@ -151,8 +157,10 @@ clear() {
  */
  */
 INLINE void ConfigVariableSearchPath::
 INLINE void ConfigVariableSearchPath::
 append_directory(const Filename &directory) {
 append_directory(const Filename &directory) {
+  _lock.lock();
   _postfix.append_directory(directory);
   _postfix.append_directory(directory);
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
 }
 }
 
 
 /**
 /**
@@ -160,8 +168,10 @@ append_directory(const Filename &directory) {
  */
  */
 INLINE void ConfigVariableSearchPath::
 INLINE void ConfigVariableSearchPath::
 prepend_directory(const Filename &directory) {
 prepend_directory(const Filename &directory) {
+  _lock.lock();
   _prefix.prepend_directory(directory);
   _prefix.prepend_directory(directory);
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
 }
 }
 
 
 /**
 /**
@@ -170,8 +180,10 @@ prepend_directory(const Filename &directory) {
  */
  */
 INLINE void ConfigVariableSearchPath::
 INLINE void ConfigVariableSearchPath::
 append_path(const std::string &path, const std::string &separator) {
 append_path(const std::string &path, const std::string &separator) {
+  _lock.lock();
   _postfix.append_path(path, separator);
   _postfix.append_path(path, separator);
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
 }
 }
 
 
 /**
 /**
@@ -180,8 +192,10 @@ append_path(const std::string &path, const std::string &separator) {
  */
  */
 INLINE void ConfigVariableSearchPath::
 INLINE void ConfigVariableSearchPath::
 append_path(const DSearchPath &path) {
 append_path(const DSearchPath &path) {
+  _lock.lock();
   _postfix.append_path(path);
   _postfix.append_path(path);
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
 }
 }
 
 
 /**
 /**
@@ -190,8 +204,10 @@ append_path(const DSearchPath &path) {
  */
  */
 INLINE void ConfigVariableSearchPath::
 INLINE void ConfigVariableSearchPath::
 prepend_path(const DSearchPath &path) {
 prepend_path(const DSearchPath &path) {
+  _lock.lock();
   _prefix.prepend_path(path);
   _prefix.prepend_path(path);
   _local_modified = initial_invalid_cache();
   _local_modified = initial_invalid_cache();
+  _lock.unlock();
 }
 }
 
 
 /**
 /**
@@ -213,9 +229,13 @@ get_num_directories() const {
 /**
 /**
  * Returns the nth directory on the search list.
  * Returns the nth directory on the search list.
  */
  */
-INLINE const Filename &ConfigVariableSearchPath::
+INLINE Filename ConfigVariableSearchPath::
 get_directory(size_t n) const {
 get_directory(size_t n) const {
-  return get_value().get_directory(n);
+  Filename dir;
+  _lock.lock();
+  dir = _cache.get_directory(n);
+  _lock.unlock();
+  return dir;
 }
 }
 
 
 /**
 /**

+ 3 - 10
dtool/src/prc/configVariableSearchPath.cxx

@@ -27,17 +27,10 @@ reload_search_path() {
   size_t num_unique_references = _core->get_num_unique_references();
   size_t num_unique_references = _core->get_num_unique_references();
   for (size_t i = 0; i < num_unique_references; i++) {
   for (size_t i = 0; i < num_unique_references; i++) {
     const ConfigDeclaration *decl = _core->get_unique_reference(i);
     const ConfigDeclaration *decl = _core->get_unique_reference(i);
-    const ConfigPage *page = decl->get_page();
 
 
-    Filename page_filename(page->get_name());
-    Filename page_dirname = page_filename.get_dirname();
-    ExecutionEnvironment::shadow_environment_variable("THIS_PRC_DIR", page_dirname.to_os_specific());
-    std::string expanded = ExecutionEnvironment::expand_string(decl->get_string_value());
-    ExecutionEnvironment::clear_shadow("THIS_PRC_DIR");
-    if (!expanded.empty()) {
-      Filename dir = Filename::from_os_specific(expanded);
-      dir.make_true_case();
-      _cache.append_directory(dir);
+    Filename fn = decl->get_filename_value();
+    if (!fn.empty()) {
+      _cache.append_directory(std::move(fn));
     }
     }
   }
   }
 
 

+ 4 - 3
dtool/src/prc/configVariableSearchPath.h

@@ -48,8 +48,8 @@ PUBLISHED:
                                   int flags = 0);
                                   int flags = 0);
   INLINE ~ConfigVariableSearchPath();
   INLINE ~ConfigVariableSearchPath();
 
 
-  INLINE operator const DSearchPath & () const;
-  INLINE const DSearchPath &get_value() const;
+  INLINE operator DSearchPath () const;
+  INLINE DSearchPath get_value() const;
   INLINE const DSearchPath &get_default_value() const;
   INLINE const DSearchPath &get_default_value() const;
   MAKE_PROPERTY(value, get_value);
   MAKE_PROPERTY(value, get_value);
   MAKE_PROPERTY(default_value, get_default_value);
   MAKE_PROPERTY(default_value, get_default_value);
@@ -66,7 +66,7 @@ PUBLISHED:
 
 
   INLINE bool is_empty() const;
   INLINE bool is_empty() const;
   INLINE size_t get_num_directories() const;
   INLINE size_t get_num_directories() const;
-  INLINE const Filename &get_directory(size_t n) const;
+  INLINE Filename get_directory(size_t n) const;
   MAKE_SEQ(get_directories, get_num_directories, get_directory);
   MAKE_SEQ(get_directories, get_num_directories, get_directory);
   MAKE_SEQ_PROPERTY(directories, get_num_directories, get_directory);
   MAKE_SEQ_PROPERTY(directories, get_num_directories, get_directory);
 
 
@@ -81,6 +81,7 @@ PUBLISHED:
 private:
 private:
   void reload_search_path();
   void reload_search_path();
 
 
+  mutable MutexImpl _lock;
   DSearchPath _default_value;
   DSearchPath _default_value;
   DSearchPath _prefix, _postfix;
   DSearchPath _prefix, _postfix;
 
 

+ 5 - 0
makepanda/confauto.in

@@ -21,6 +21,11 @@
 
 
 load-file-type egg pandaegg
 load-file-type egg pandaegg
 
 
+# If we built with Assimp support, we can enable the Assimp loader,
+# which allows us to load many model formats natively.
+
+load-file-type p3assimp
+
 # These entries work very similar to load-file-type, except they are
 # These entries work very similar to load-file-type, except they are
 # used by the MovieVideo and MovieAudio code to determine which module
 # used by the MovieVideo and MovieAudio code to determine which module
 # should be loaded in order to decode files of the given extension.
 # should be loaded in order to decode files of the given extension.

+ 7 - 4
makepanda/makepanda.py

@@ -1010,8 +1010,6 @@ if (COMPILER=="GCC"):
 
 
     if GetTarget() == 'darwin':
     if GetTarget() == 'darwin':
         LibName("ALWAYS", "-framework AppKit")
         LibName("ALWAYS", "-framework AppKit")
-        if (PkgSkip("OPENCV")==0):
-            LibName("OPENCV", "-framework QuickTime")
         LibName("AGL", "-framework AGL")
         LibName("AGL", "-framework AGL")
         LibName("CARBON", "-framework Carbon")
         LibName("CARBON", "-framework Carbon")
         LibName("COCOA", "-framework Cocoa")
         LibName("COCOA", "-framework Cocoa")
@@ -1398,9 +1396,10 @@ def CompileCxx(obj,src,opts):
                     # Work around Apple compiler bug.
                     # Work around Apple compiler bug.
                     cmd += " -U__EXCEPTIONS"
                     cmd += " -U__EXCEPTIONS"
 
 
-            if 'RTTI' not in opts:
+            target = GetTarget()
+            if 'RTTI' not in opts and target != "darwin":
                 # We always disable RTTI on Android for memory usage reasons.
                 # We always disable RTTI on Android for memory usage reasons.
-                if optlevel >= 4 or GetTarget() == "android":
+                if optlevel >= 4 or target == "android":
                     cmd += " -fno-rtti"
                     cmd += " -fno-rtti"
 
 
         if ('SSE2' in opts or not PkgSkip("SSE2")) and not arch.startswith("arm") and arch != 'aarch64':
         if ('SSE2' in opts or not PkgSkip("SSE2")) and not arch.startswith("arm") and arch != 'aarch64':
@@ -2979,6 +2978,9 @@ else:
     # otherwise, disable it.
     # otherwise, disable it.
     confautoprc = confautoprc.replace('#st#', '#')
     confautoprc = confautoprc.replace('#st#', '#')
 
 
+if PkgSkip("ASSIMP"):
+    confautoprc = confautoprc.replace("load-file-type p3assimp", "#load-file-type p3assimp")
+
 if (os.path.isfile("makepanda/myconfig.in")):
 if (os.path.isfile("makepanda/myconfig.in")):
     configprc = ReadFile("makepanda/myconfig.in")
     configprc = ReadFile("makepanda/myconfig.in")
 else:
 else:
@@ -3645,6 +3647,7 @@ IGATEFILES += [
     "dSearchPath.h",
     "dSearchPath.h",
     "executionEnvironment.h",
     "executionEnvironment.h",
     "textEncoder.h",
     "textEncoder.h",
+    "textEncoder_ext.h",
     "filename.h",
     "filename.h",
     "filename_ext.h",
     "filename_ext.h",
     "globPattern.h",
     "globPattern.h",

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

@@ -248,8 +248,8 @@ inline std::ostream &operator <<(std::ostream &out, const PartBundle &bundle) {
   return out;
   return out;
 }
 }
 
 
-std::ostream &operator <<(std::ostream &out, PartBundle::BlendType blend_type);
-std::istream &operator >>(std::istream &in, PartBundle::BlendType &blend_type);
+EXPCL_PANDA_CHAN std::ostream &operator <<(std::ostream &out, PartBundle::BlendType blend_type);
+EXPCL_PANDA_CHAN std::istream &operator >>(std::istream &in, PartBundle::BlendType &blend_type);
 
 
 #include "partBundle.I"
 #include "partBundle.I"
 
 

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

@@ -12,6 +12,7 @@
  */
  */
 
 
 #import "cocoaPandaApp.h"
 #import "cocoaPandaApp.h"
+#include "config_cocoadisplay.h"
 
 
 @implementation CocoaPandaApp
 @implementation CocoaPandaApp
 - (void) sendEvent: (NSEvent *) event {
 - (void) sendEvent: (NSEvent *) event {

+ 9 - 2
panda/src/display/graphicsEngine.cxx

@@ -593,8 +593,7 @@ remove_all_windows() {
   Windows old_windows;
   Windows old_windows;
   old_windows.swap(_windows);
   old_windows.swap(_windows);
   Windows::iterator wi;
   Windows::iterator wi;
-  for (wi = old_windows.begin(); wi != old_windows.end(); ++wi) {
-    GraphicsOutput *win = (*wi);
+  for (GraphicsOutput *win : old_windows) {
     nassertv(win != nullptr);
     nassertv(win != nullptr);
     do_remove_window(win, current_thread);
     do_remove_window(win, current_thread);
     GraphicsStateGuardian *gsg = win->get_gsg();
     GraphicsStateGuardian *gsg = win->get_gsg();
@@ -605,6 +604,14 @@ remove_all_windows() {
 
 
   {
   {
     MutexHolder new_windows_holder(_new_windows_lock, current_thread);
     MutexHolder new_windows_holder(_new_windows_lock, current_thread);
+    for (GraphicsOutput *win : _new_windows) {
+      nassertv(win != nullptr);
+      do_remove_window(win, current_thread);
+      GraphicsStateGuardian *gsg = win->get_gsg();
+      if (gsg != nullptr) {
+        gsg->release_all();
+      }
+    }
     _new_windows.clear();
     _new_windows.clear();
   }
   }
 
 

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

@@ -2722,7 +2722,7 @@ do_issue_color_scale() {
   }
   }
 
 
   if (_alpha_scale_via_texture && !_has_scene_graph_color &&
   if (_alpha_scale_via_texture && !_has_scene_graph_color &&
-      target_color_scale->has_alpha_scale()) {
+      _vertex_colors_enabled && target_color_scale->has_alpha_scale()) {
     // This color scale will set a special texture--so again, clear the
     // This color scale will set a special texture--so again, clear the
     // texture.
     // texture.
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
@@ -3168,6 +3168,17 @@ determine_light_color_scale() {
                                 _scene_graph_color[3] * _current_color_scale[3]);
                                 _scene_graph_color[3] * _current_color_scale[3]);
     }
     }
 
 
+  } else if (!_vertex_colors_enabled) {
+    // We don't have a scene graph color, but we don't want to enable vertex
+    // colors either, so we still need to force a white material color in
+    // absence of any other color.
+    _has_material_force_color = true;
+    _material_force_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+    _light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
+    if (!_color_blend_involves_color_scale && _color_scale_enabled) {
+      _material_force_color.componentwise_mult(_current_color_scale);
+    }
+
   } else {
   } else {
     // Otherise, leave the materials alone, but we might still scale the
     // Otherise, leave the materials alone, but we might still scale the
     // lights.
     // lights.

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

@@ -32,6 +32,7 @@ class GraphicsWindow;
 class EXPCL_PANDA_DISPLAY GraphicsWindowProc {
 class EXPCL_PANDA_DISPLAY GraphicsWindowProc {
 public:
 public:
   GraphicsWindowProc();
   GraphicsWindowProc();
+  virtual ~GraphicsWindowProc() = default;
 #if defined(__WIN32__) || defined(_WIN32)
 #if defined(__WIN32__) || defined(_WIN32)
   virtual LONG wnd_proc(GraphicsWindow* graphicsWindow, HWND hwnd,
   virtual LONG wnd_proc(GraphicsWindow* graphicsWindow, HWND hwnd,
                         UINT msg, WPARAM wparam, LPARAM lparam);
                         UINT msg, WPARAM wparam, LPARAM lparam);

+ 32 - 21
panda/src/display/standardMunger.cxx

@@ -12,10 +12,14 @@
  */
  */
 
 
 #include "standardMunger.h"
 #include "standardMunger.h"
-#include "renderState.h"
-#include "graphicsStateGuardian.h"
+
 #include "config_gobj.h"
 #include "config_gobj.h"
+
 #include "displayRegion.h"
 #include "displayRegion.h"
+#include "graphicsStateGuardian.h"
+#include "lightAttrib.h"
+#include "materialAttrib.h"
+#include "renderState.h"
 
 
 TypeHandle StandardMunger::_type_handle;
 TypeHandle StandardMunger::_type_handle;
 
 
@@ -36,7 +40,8 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
   _munge_color(false),
   _munge_color(false),
   _munge_color_scale(false),
   _munge_color_scale(false),
   _auto_shader(false),
   _auto_shader(false),
-  _shader_skinning(false)
+  _shader_skinning(false),
+  _remove_material(false)
 {
 {
   const ShaderAttrib *shader_attrib;
   const ShaderAttrib *shader_attrib;
   state->get_attrib_def(shader_attrib);
   state->get_attrib_def(shader_attrib);
@@ -54,24 +59,10 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
     const ColorScaleAttrib *color_scale_attrib;
     const ColorScaleAttrib *color_scale_attrib;
 
 
     if (state->get_attrib(color_attrib) &&
     if (state->get_attrib(color_attrib) &&
-        color_attrib->get_color_type() == ColorAttrib::T_flat) {
-
-      if (!get_gsg()->get_color_scale_via_lighting()) {
-        // We only need to munge the color directly if the GSG says it can't
-        // cheat the color via lighting (presumably, in this case, by applying
-        // a material).
-        _color = color_attrib->get_color();
-        if (state->get_attrib(color_scale_attrib) &&
-            color_scale_attrib->has_scale()) {
-          const LVecBase4 &cs = color_scale_attrib->get_scale();
-          _color.set(_color[0] * cs[0],
-                     _color[1] * cs[1],
-                     _color[2] * cs[2],
-                     _color[3] * cs[3]);
-        }
-        _munge_color = true;
-        _should_munge_state = true;
-      }
+        color_attrib->get_color_type() != ColorAttrib::T_vertex) {
+
+      // In this case, we don't need to munge anything as we can apply the
+      // color and color scale via glColor4f.
 
 
     } else if (state->get_attrib(color_scale_attrib) &&
     } else if (state->get_attrib(color_scale_attrib) &&
                color_scale_attrib->has_scale()) {
                color_scale_attrib->has_scale()) {
@@ -94,6 +85,19 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
       // effort to detect this contrived situation and handle it correctly.
       // effort to detect this contrived situation and handle it correctly.
     }
     }
   }
   }
+
+  // If we have no lights but do have a material, we will need to remove it so
+  // that it won't appear when we enable color scale via lighting.
+  const LightAttrib *light_attrib;
+  const MaterialAttrib *material_attrib;
+  if (get_gsg()->get_color_scale_via_lighting() &&
+      (!state->get_attrib(light_attrib) || !light_attrib->has_any_on_light()) &&
+      state->get_attrib(material_attrib) &&
+      material_attrib->get_material() != nullptr &&
+      shader_attrib->get_shader() == nullptr) {
+    _remove_material = true;
+    _should_munge_state = true;
+  }
 }
 }
 
 
 /**
 /**
@@ -291,6 +295,9 @@ compare_to_impl(const GeomMunger *other) const {
   if (_auto_shader != om->_auto_shader) {
   if (_auto_shader != om->_auto_shader) {
     return (int)_auto_shader - (int)om->_auto_shader;
     return (int)_auto_shader - (int)om->_auto_shader;
   }
   }
+  if (_remove_material != om->_remove_material) {
+    return (int)_remove_material - (int)om->_remove_material;
+  }
 
 
   return StateMunger::compare_to_impl(other);
   return StateMunger::compare_to_impl(other);
 }
 }
@@ -344,5 +351,9 @@ munge_state_impl(const RenderState *state) {
     munged_state = munged_state->remove_attrib(ColorScaleAttrib::get_class_slot());
     munged_state = munged_state->remove_attrib(ColorScaleAttrib::get_class_slot());
   }
   }
 
 
+  if (_remove_material) {
+    munged_state = munged_state->remove_attrib(MaterialAttrib::get_class_slot());
+  }
+
   return munged_state;
   return munged_state;
 }
 }

+ 4 - 2
panda/src/display/standardMunger.h

@@ -51,11 +51,13 @@ private:
   NumericType _numeric_type;
   NumericType _numeric_type;
   Contents _contents;
   Contents _contents;
 
 
-  bool _munge_color;
-  bool _munge_color_scale;
   bool _auto_shader;
   bool _auto_shader;
   bool _shader_skinning;
   bool _shader_skinning;
+  bool _remove_material;
 
 
+protected:
+  bool _munge_color;
+  bool _munge_color_scale;
   LColor _color;
   LColor _color;
   LVecBase4 _color_scale;
   LVecBase4 _color_scale;
 
 

+ 2 - 0
panda/src/display/subprocessWindow.cxx

@@ -18,6 +18,8 @@
 #include "graphicsEngine.h"
 #include "graphicsEngine.h"
 #include "config_display.h"
 #include "config_display.h"
 #include "nativeWindowHandle.h"
 #include "nativeWindowHandle.h"
+#include "mouseButton.h"
+#include "throw_event.h"
 
 
 using std::string;
 using std::string;
 
 

+ 0 - 31
panda/src/dxgsg9/dxGeomMunger9.I

@@ -10,34 +10,3 @@
  * @author drose
  * @author drose
  * @date 2005-03-11
  * @date 2005-03-11
  */
  */
-
-/**
- *
- */
-INLINE DXGeomMunger9::
-DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state) :
-  StandardMunger(gsg, state, 1, NT_packed_dabc, C_color),
-  _texture(nullptr),
-  _tex_gen(nullptr)
-{
-  const TextureAttrib *texture = nullptr;
-  const TexGenAttrib *tex_gen = nullptr;
-  state->get_attrib(texture);
-  state->get_attrib(tex_gen);
-  _texture = texture;
-  _tex_gen = tex_gen;
-
-  _filtered_texture = nullptr;
-  _reffed_filtered_texture = false;
-  if (texture != nullptr) {
-    _filtered_texture = texture->filter_to_max(gsg->get_max_texture_stages());
-    if (_filtered_texture != texture) {
-      _filtered_texture->ref();
-      _reffed_filtered_texture = true;
-    }
-  }
-  // Set a callback to unregister ourselves when either the Texture or the
-  // TexGen object gets deleted.
-  _texture.add_callback(this);
-  _tex_gen.add_callback(this);
-}

+ 60 - 0
panda/src/dxgsg9/dxGeomMunger9.cxx

@@ -19,6 +19,66 @@
 GeomMunger *DXGeomMunger9::_deleted_chain = nullptr;
 GeomMunger *DXGeomMunger9::_deleted_chain = nullptr;
 TypeHandle DXGeomMunger9::_type_handle;
 TypeHandle DXGeomMunger9::_type_handle;
 
 
+/**
+ *
+ */
+DXGeomMunger9::
+DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state) :
+  StandardMunger(gsg, state, 1, NT_packed_dabc, C_color),
+  _texture(nullptr),
+  _tex_gen(nullptr)
+{
+  const TextureAttrib *texture = nullptr;
+  const TexGenAttrib *tex_gen = nullptr;
+  state->get_attrib(texture);
+  state->get_attrib(tex_gen);
+  _texture = texture;
+  _tex_gen = tex_gen;
+
+  if (!gsg->get_color_scale_via_lighting()) {
+    // We might need to munge the colors, if we are overriding the vertex
+    // colors and the GSG can't cheat the color via lighting.
+
+    const ColorAttrib *color_attrib;
+    const ShaderAttrib *shader_attrib;
+    state->get_attrib_def(shader_attrib);
+
+    if (!shader_attrib->auto_shader() &&
+        shader_attrib->get_shader() == nullptr &&
+        state->get_attrib(color_attrib) &&
+        color_attrib->get_color_type() != ColorAttrib::T_vertex) {
+
+      if (color_attrib->get_color_type() == ColorAttrib::T_off) {
+        _color.set(1, 1, 1, 1);
+      } else {
+        _color = color_attrib->get_color();
+      }
+
+      const ColorScaleAttrib *color_scale_attrib;
+      if (state->get_attrib(color_scale_attrib) &&
+          color_scale_attrib->has_scale()) {
+        _color.componentwise_mult(color_scale_attrib->get_scale());
+      }
+      _munge_color = true;
+      _should_munge_state = true;
+    }
+  }
+
+  _filtered_texture = nullptr;
+  _reffed_filtered_texture = false;
+  if (texture != nullptr) {
+    _filtered_texture = texture->filter_to_max(gsg->get_max_texture_stages());
+    if (_filtered_texture != texture) {
+      _filtered_texture->ref();
+      _reffed_filtered_texture = true;
+    }
+  }
+  // Set a callback to unregister ourselves when either the Texture or the
+  // TexGen object gets deleted.
+  _texture.add_callback(this);
+  _tex_gen.add_callback(this);
+}
+
 /**
 /**
  *
  *
  */
  */

+ 1 - 1
panda/src/dxgsg9/dxGeomMunger9.h

@@ -28,7 +28,7 @@
  */
  */
 class EXPCL_PANDADX DXGeomMunger9 : public StandardMunger, public WeakPointerCallback {
 class EXPCL_PANDADX DXGeomMunger9 : public StandardMunger, public WeakPointerCallback {
 public:
 public:
-  INLINE DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state);
+  DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state);
   virtual ~DXGeomMunger9();
   virtual ~DXGeomMunger9();
   ALLOC_DELETED_CHAIN(DXGeomMunger9);
   ALLOC_DELETED_CHAIN(DXGeomMunger9);
 
 

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

@@ -140,6 +140,7 @@ DXGraphicsStateGuardian9(GraphicsEngine *engine, GraphicsPipe *pipe) :
 
 
   _last_fvf = 0;
   _last_fvf = 0;
   _num_bound_streams = 0;
   _num_bound_streams = 0;
+  _white_vbuffer = nullptr;
 
 
   _vertex_shader_version_major = 0;
   _vertex_shader_version_major = 0;
   _vertex_shader_version_minor = 0;
   _vertex_shader_version_minor = 0;
@@ -798,9 +799,9 @@ clear(DrawableRegion *clearable) {
     main_flags |=  D3DCLEAR_TARGET;
     main_flags |=  D3DCLEAR_TARGET;
   }
   }
 
 
-  if (clearable->get_clear_depth_active()) {
+  if (clearable->get_clear_depth_active() &&
+      _screen->_presentation_params.EnableAutoDepthStencil) {
     aux_flags |=  D3DCLEAR_ZBUFFER;
     aux_flags |=  D3DCLEAR_ZBUFFER;
-    nassertv(_screen->_presentation_params.EnableAutoDepthStencil);
   }
   }
 
 
   if (clearable->get_clear_stencil_active()) {
   if (clearable->get_clear_stencil_active()) {
@@ -4545,6 +4546,11 @@ reset_d3d_device(D3DPRESENT_PARAMETERS *presentation_params,
     release_all_vertex_buffers();
     release_all_vertex_buffers();
     release_all_index_buffers();
     release_all_index_buffers();
 
 
+    if (_white_vbuffer != nullptr) {
+      _white_vbuffer->Release();
+      _white_vbuffer = nullptr;
+    }
+
     // must be called before reset
     // must be called before reset
     Thread *current_thread = Thread::get_current_thread();
     Thread *current_thread = Thread::get_current_thread();
     _prepared_objects->begin_frame(this, current_thread);
     _prepared_objects->begin_frame(this, current_thread);
@@ -5404,6 +5410,40 @@ set_cg_device(LPDIRECT3DDEVICE9 cg_device) {
 #endif // HAVE_CG
 #endif // HAVE_CG
 }
 }
 
 
+/**
+ * Returns a vertex buffer containing only a full-white color.
+ */
+LPDIRECT3DVERTEXBUFFER9 DXGraphicsStateGuardian9::
+get_white_vbuffer() {
+  if (_white_vbuffer != nullptr) {
+    return _white_vbuffer;
+  }
+
+  LPDIRECT3DVERTEXBUFFER9 vbuffer;
+  HRESULT hr;
+  hr = _screen->_d3d_device->CreateVertexBuffer(sizeof(D3DCOLOR), D3DUSAGE_WRITEONLY, D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &vbuffer, nullptr);
+
+  if (FAILED(hr)) {
+    dxgsg9_cat.error()
+      << "CreateVertexBuffer failed" << D3DERRORSTRING(hr);
+    return nullptr;
+  }
+
+  D3DCOLOR *local_pointer;
+  hr = vbuffer->Lock(0, sizeof(D3DCOLOR), (void **) &local_pointer, D3DLOCK_DISCARD);
+  if (FAILED(hr)) {
+    dxgsg9_cat.error()
+      << "VertexBuffer::Lock failed" << D3DERRORSTRING(hr);
+    return false;
+  }
+
+  *local_pointer = D3DCOLOR_ARGB(255, 255, 255, 255);
+
+  vbuffer->Unlock();
+  _white_vbuffer = vbuffer;
+  return vbuffer;
+}
+
 typedef std::string KEY;
 typedef std::string KEY;
 
 
 typedef struct _KEY_ELEMENT
 typedef struct _KEY_ELEMENT

+ 2 - 6
panda/src/dxgsg9/dxGraphicsStateGuardian9.h

@@ -168,6 +168,7 @@ public:
   static void set_cg_device(LPDIRECT3DDEVICE9 cg_device);
   static void set_cg_device(LPDIRECT3DDEVICE9 cg_device);
   virtual bool get_supports_cg_profile(const std::string &name) const;
   virtual bool get_supports_cg_profile(const std::string &name) const;
 
 
+  LPDIRECT3DVERTEXBUFFER9 get_white_vbuffer();
 
 
 protected:
 protected:
   void do_issue_transform();
   void do_issue_transform();
@@ -274,12 +275,6 @@ protected:
 
 
   RenderBuffer::Type _cur_read_pixel_buffer;  // source for copy_pixel_buffer operation
   RenderBuffer::Type _cur_read_pixel_buffer;  // source for copy_pixel_buffer operation
 
 
-  PN_stdfloat _material_ambient;
-  PN_stdfloat _material_diffuse;
-  PN_stdfloat _material_specular;
-  PN_stdfloat _material_shininess;
-  PN_stdfloat _material_emission;
-
   enum DxgsgFogType {
   enum DxgsgFogType {
     None,
     None,
     PerVertexFog=D3DRS_FOGVERTEXMODE,
     PerVertexFog=D3DRS_FOGVERTEXMODE,
@@ -320,6 +315,7 @@ protected:
 
 
   DWORD _last_fvf;
   DWORD _last_fvf;
   int _num_bound_streams;
   int _num_bound_streams;
+  LPDIRECT3DVERTEXBUFFER9 _white_vbuffer;
 
 
   // Cache the data necessary to bind each particular light each frame, so if
   // Cache the data necessary to bind each particular light each frame, so if
   // we bind a given light multiple times, we only have to compute its data
   // we bind a given light multiple times, we only have to compute its data

+ 23 - 0
panda/src/dxgsg9/dxShaderContext9.cxx

@@ -390,6 +390,8 @@ update_shader_vertex_arrays(DXShaderContext9 *prev, GSG *gsg, bool force) {
     // arrays ("streams"), and we repeatedly iterate the parameters to pull
     // arrays ("streams"), and we repeatedly iterate the parameters to pull
     // out only those for a single stream.
     // out only those for a single stream.
 
 
+    bool apply_white_color = false;
+
     int number_of_arrays = gsg->_data_reader->get_num_arrays();
     int number_of_arrays = gsg->_data_reader->get_num_arrays();
     for (int array_index = 0; array_index < number_of_arrays; ++array_index) {
     for (int array_index = 0; array_index < number_of_arrays; ++array_index) {
       const GeomVertexArrayDataHandle* array_reader =
       const GeomVertexArrayDataHandle* array_reader =
@@ -423,6 +425,11 @@ update_shader_vertex_arrays(DXShaderContext9 *prev, GSG *gsg, bool force) {
           }
           }
         }
         }
 
 
+        if (name == InternalName::get_color() && !gsg->_vertex_colors_enabled) {
+          apply_white_color = true;
+          continue;
+        }
+
         const GeomVertexArrayDataHandle *param_array_reader;
         const GeomVertexArrayDataHandle *param_array_reader;
         Geom::NumericType numeric_type;
         Geom::NumericType numeric_type;
         int num_values, start, stride;
         int num_values, start, stride;
@@ -435,6 +442,9 @@ update_shader_vertex_arrays(DXShaderContext9 *prev, GSG *gsg, bool force) {
           // shader parameter, which can cause Bad Things to happen so I'd
           // shader parameter, which can cause Bad Things to happen so I'd
           // like to at least get a hint as to what's gone wrong.
           // like to at least get a hint as to what's gone wrong.
           dxgsg9_cat.info() << "Geometry contains no data for shader parameter " << *name << "\n";
           dxgsg9_cat.info() << "Geometry contains no data for shader parameter " << *name << "\n";
+          if (name == InternalName::get_color()) {
+            apply_white_color = true;
+          }
           continue;
           continue;
         }
         }
 
 
@@ -564,6 +574,19 @@ update_shader_vertex_arrays(DXShaderContext9 *prev, GSG *gsg, bool force) {
 
 
     _num_bound_streams = number_of_arrays;
     _num_bound_streams = number_of_arrays;
 
 
+    if (apply_white_color) {
+      // The shader needs a vertex color, but vertex colors are disabled.
+      // Bind a vertex buffer containing only one white colour.
+      int array_index = number_of_arrays;
+      LPDIRECT3DVERTEXBUFFER9 vbuffer = gsg->get_white_vbuffer();
+      hr = device->SetStreamSource(array_index, vbuffer, 0, 0);
+      if (FAILED(hr)) {
+        dxgsg9_cat.error() << "SetStreamSource failed" << D3DERRORSTRING(hr);
+      }
+      vertex_element_array->add_diffuse_color_vertex_element(array_index, 0);
+      ++_num_bound_streams;
+    }
+
     if (_vertex_element_array != nullptr &&
     if (_vertex_element_array != nullptr &&
         _vertex_element_array->add_end_vertex_element()) {
         _vertex_element_array->add_end_vertex_element()) {
       if (dxgsg9_cat.is_debug()) {
       if (dxgsg9_cat.is_debug()) {

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

@@ -1229,7 +1229,10 @@ init_resized_window() {
   DWORD flags;
   DWORD flags;
   D3DCOLOR clear_color;
   D3DCOLOR clear_color;
 
 
-  flags = D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER;
+  flags = D3DCLEAR_TARGET;
+  if (_fb_properties.get_depth_bits() > 0) {
+    flags |= D3DCLEAR_ZBUFFER;
+  }
   clear_color = 0x00000000;
   clear_color = 0x00000000;
   hr = _wcontext._d3d_device-> Clear (0, nullptr, flags, clear_color, 0.0f, 0);
   hr = _wcontext._d3d_device-> Clear (0, nullptr, flags, clear_color, 0.0f, 0);
   if (FAILED(hr)) {
   if (FAILED(hr)) {

+ 1 - 1
panda/src/egg/eggMesher.h

@@ -30,7 +30,7 @@
  * connectivity, and generates a set of EggTriangleStrips that represent the
  * connectivity, and generates a set of EggTriangleStrips that represent the
  * same geometry.
  * same geometry.
  */
  */
-class EggMesher {
+class EXPCL_PANDA_EGG EggMesher {
 public:
 public:
   EggMesher();
   EggMesher();
 
 

+ 1 - 1
panda/src/egg/eggMesherEdge.h

@@ -26,7 +26,7 @@ class EggMesherStrip;
  * connected triangles.  The edge is actually represented as a pair of vertex
  * connected triangles.  The edge is actually represented as a pair of vertex
  * indices into the same vertex pool.
  * indices into the same vertex pool.
  */
  */
-class EggMesherEdge {
+class EXPCL_PANDA_EGG EggMesherEdge {
 public:
 public:
   INLINE EggMesherEdge(int vi_a, int vi_b);
   INLINE EggMesherEdge(int vi_a, int vi_b);
   INLINE EggMesherEdge(const EggMesherEdge &copy);
   INLINE EggMesherEdge(const EggMesherEdge &copy);

+ 1 - 1
panda/src/egg/eggMesherFanMaker.h

@@ -31,7 +31,7 @@ class EggMesher;
  * This class is used by EggMesher::find_fans() to attempt to make an
  * This class is used by EggMesher::find_fans() to attempt to make an
  * EggTriangleFan out of the polygons connected to the indicated vertex.
  * EggTriangleFan out of the polygons connected to the indicated vertex.
  */
  */
-class EggMesherFanMaker {
+class EXPCL_PANDA_EGG EggMesherFanMaker {
 public:
 public:
   typedef plist<const EggMesherEdge *> Edges;
   typedef plist<const EggMesherEdge *> Edges;
   typedef plist<EggMesherStrip *> Strips;
   typedef plist<EggMesherStrip *> Strips;

+ 1 - 1
panda/src/egg/eggMesherStrip.h

@@ -27,7 +27,7 @@ class EggMesherEdge;
  * mesher.  It might also represent a single polygon such as a triangle or
  * mesher.  It might also represent a single polygon such as a triangle or
  * quad, since that's how strips generally start out.
  * quad, since that's how strips generally start out.
  */
  */
-class EggMesherStrip {
+class EXPCL_PANDA_EGG EggMesherStrip {
 public:
 public:
   enum PrimType {
   enum PrimType {
     PT_poly,
     PT_poly,

+ 1 - 1
panda/src/egg2pg/eggBinner.h

@@ -27,7 +27,7 @@ class EggLoader;
  * It is used to collect similar polygons together for a Geom, as well as to
  * It is used to collect similar polygons together for a Geom, as well as to
  * group related LOD children together under a single LOD node.
  * group related LOD children together under a single LOD node.
  */
  */
-class EggBinner : public EggBinMaker {
+class EXPCL_PANDA_EGG2PG EggBinner : public EggBinMaker {
 public:
 public:
   // The BinNumber serves to identify why a particular EggBin was created.
   // The BinNumber serves to identify why a particular EggBin was created.
   enum BinNumber {
   enum BinNumber {

+ 1 - 1
panda/src/egg2pg/eggLoader.h

@@ -64,7 +64,7 @@ class CharacterMaker;
  *
  *
  * This class isn't exported from this package.
  * This class isn't exported from this package.
  */
  */
-class EggLoader {
+class EXPCL_PANDA_EGG2PG EggLoader {
 public:
 public:
   EggLoader();
   EggLoader();
   EggLoader(const EggData *data);
   EggLoader(const EggData *data);

+ 1 - 1
panda/src/egg2pg/eggRenderState.h

@@ -36,7 +36,7 @@ class EggMaterial;
  * should be assigned to each primitive.  It is assigned to EggPrimitive
  * should be assigned to each primitive.  It is assigned to EggPrimitive
  * objects via the EggBinner.
  * objects via the EggBinner.
  */
  */
-class EggRenderState : public EggUserData {
+class EXPCL_PANDA_EGG2PG EggRenderState : public EggUserData {
 public:
 public:
   INLINE EggRenderState(EggLoader &loader);
   INLINE EggRenderState(EggLoader &loader);
   INLINE void add_attrib(const RenderAttrib *attrib);
   INLINE void add_attrib(const RenderAttrib *attrib);

+ 1 - 1
panda/src/egg2pg/eggSaver.h

@@ -50,7 +50,7 @@ class EggVertex;
  * complete (some Panda or egg constructs are not fully supported by this
  * complete (some Panda or egg constructs are not fully supported by this
  * class).
  * class).
  */
  */
-class EggSaver {
+class EXPCL_PANDA_EGG2PG EggSaver {
 PUBLISHED:
 PUBLISHED:
   EggSaver(EggData *data = nullptr);
   EggSaver(EggData *data = nullptr);
 
 

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

@@ -99,6 +99,27 @@ PUBLISHED:
 
 
   virtual void output(std::ostream &out) const;
   virtual void output(std::ostream &out) const;
 
 
+PUBLISHED:
+  MAKE_PROPERTY(state, get_state);
+  MAKE_PROPERTY(alive, is_alive);
+  MAKE_PROPERTY(manager, get_manager);
+
+  // The name of this task.
+  MAKE_PROPERTY(name, get_name, set_name);
+
+  // This is a number guaranteed to be unique for each different AsyncTask
+  // object in the universe.
+  MAKE_PROPERTY(id, get_task_id);
+
+  MAKE_PROPERTY(task_chain, get_task_chain, set_task_chain);
+  MAKE_PROPERTY(sort, get_sort, set_sort);
+  MAKE_PROPERTY(priority, get_priority, set_priority);
+  MAKE_PROPERTY(done_event, get_done_event, set_done_event);
+
+  MAKE_PROPERTY(dt, get_dt);
+  MAKE_PROPERTY(max_dt, get_max_dt);
+  MAKE_PROPERTY(average_dt, get_average_dt);
+
 protected:
 protected:
   void jump_to_task_chain(AsyncTaskManager *manager);
   void jump_to_task_chain(AsyncTaskManager *manager);
   DoneStatus unlock_and_do_task();
   DoneStatus unlock_and_do_task();

+ 0 - 7
panda/src/event/pythonTask.h

@@ -61,9 +61,6 @@ PUBLISHED:
   int __clear__();
   int __clear__();
 
 
 PUBLISHED:
 PUBLISHED:
-  // The name of this task.
-  MAKE_PROPERTY(name, get_name, set_name);
-
   // The amount of seconds that have elapsed since the task was started,
   // The amount of seconds that have elapsed since the task was started,
   // according to the task manager's clock.
   // according to the task manager's clock.
   MAKE_PROPERTY(time, get_elapsed_time);
   MAKE_PROPERTY(time, get_elapsed_time);
@@ -88,10 +85,6 @@ PUBLISHED:
   // according to the task manager's clock.
   // according to the task manager's clock.
   MAKE_PROPERTY(frame, get_elapsed_frames);
   MAKE_PROPERTY(frame, get_elapsed_frames);
 
 
-  // This is a number guaranteed to be unique for each different AsyncTask
-  // object in the universe.
-  MAKE_PROPERTY(id, get_task_id);
-
   // This is a special variable to hold the instance dictionary in which
   // This is a special variable to hold the instance dictionary in which
   // custom variables may be stored.
   // custom variables may be stored.
   PyObject *__dict__;
   PyObject *__dict__;

+ 2 - 1
panda/src/express/pointerToArray_ext.I

@@ -38,7 +38,8 @@ INLINE void set_matrix_view(Py_buffer &view, int flags, int length, int size, bo
   } else if (size == 4 && double_prec) {
   } else if (size == 4 && double_prec) {
     mat_size = sizeof(UnalignedLMatrix4d);
     mat_size = sizeof(UnalignedLMatrix4d);
   } else {
   } else {
-    assert(false);
+    nassertv_always(false);
+    return; // Make sure compiler knows control flow doesn't proceed.
   }
   }
 
 
   view.len = length * mat_size;
   view.len = length * mat_size;

+ 5 - 5
panda/src/express/pointerToArray_ext.h

@@ -97,11 +97,11 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 // Ugh... MSVC needs this because they still don't have a decent linker.
 // Ugh... MSVC needs this because they still don't have a decent linker.
-#include "PTA_uchar.h"
-#include "PTA_ushort.h"
-#include "PTA_float.h"
-#include "PTA_double.h"
-#include "PTA_int.h"
+#include "pta_uchar.h"
+#include "pta_ushort.h"
+#include "pta_float.h"
+#include "pta_double.h"
+#include "pta_int.h"
 
 
 template class EXPORT_THIS Extension<PTA_uchar>;
 template class EXPORT_THIS Extension<PTA_uchar>;
 template class EXPORT_THIS Extension<PTA_ushort>;
 template class EXPORT_THIS Extension<PTA_ushort>;

+ 11 - 9
panda/src/glstuff/glCgShaderContext_src.cxx

@@ -226,12 +226,14 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte
         if (!resource) {
         if (!resource) {
           resource = "unknown";
           resource = "unknown";
         }
         }
-        GLCAT.error()
-          << "Could not find Cg varying " << cgGetParameterName(p);
-        if (attribname) {
-          GLCAT.error(false) << " : " << attribname;
+        if (GLCAT.is_debug()) {
+          GLCAT.debug()
+            << "Could not find Cg varying " << cgGetParameterName(p);
+          if (attribname) {
+            GLCAT.debug(false) << " : " << attribname;
+          }
+          GLCAT.debug(false) << " (" << resource << ") in the compiled GLSL program.\n";
         }
         }
-        GLCAT.error(false) << " (" << resource << ") in the compiled GLSL program.\n";
 
 
       } else if (loc != 0 && bind._id._name == "vtx_position") {
       } else if (loc != 0 && bind._id._name == "vtx_position") {
         // We really have to bind the vertex position to attribute 0, since
         // We really have to bind the vertex position to attribute 0, since
@@ -312,10 +314,10 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte
         GLCAT.debug(false)
         GLCAT.debug(false)
           << " is bound to a conventional attribute (" << resource << ")\n";
           << " is bound to a conventional attribute (" << resource << ")\n";
       }
       }
-    }
-    if (loc == CA_unknown) {
-      // Suggest fix to developer.
-      GLCAT.error() << "Try using a different semantic.\n";
+      if (loc == CA_unknown) {
+        // Suggest fix to developer.
+        GLCAT.debug() << "Try using a different semantic.\n";
+      }
     }
     }
 #endif
 #endif
 
 

+ 7 - 0
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -283,6 +283,13 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     rebuild_bitplanes();
     rebuild_bitplanes();
   }
   }
 
 
+  // The host window may not have had sRGB enabled, so we need to do this.
+#ifndef OPENGLES
+  if (get_fb_properties().get_srgb_color()) {
+    glEnable(GL_FRAMEBUFFER_SRGB);
+  }
+#endif
+
   _gsg->set_current_properties(&get_fb_properties());
   _gsg->set_current_properties(&get_fb_properties());
   report_my_gl_errors();
   report_my_gl_errors();
   return true;
   return true;

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

@@ -4392,7 +4392,8 @@ update_standard_vertex_arrays(bool force) {
       GLPf(Color4)(1.0f, 1.0f, 1.0f, 1.0f);
       GLPf(Color4)(1.0f, 1.0f, 1.0f, 1.0f);
     } else
     } else
 #endif // NDEBUG
 #endif // NDEBUG
-      if (_data_reader->get_color_info(array_reader, num_values, numeric_type,
+      if (_vertex_colors_enabled &&
+          _data_reader->get_color_info(array_reader, num_values, numeric_type,
                                        start, stride)) {
                                        start, stride)) {
         if (!setup_array_data(client_pointer, array_reader, force)) {
         if (!setup_array_data(client_pointer, array_reader, force)) {
           return false;
           return false;
@@ -4409,7 +4410,13 @@ update_standard_vertex_arrays(bool force) {
         glDisableClientState(GL_COLOR_ARRAY);
         glDisableClientState(GL_COLOR_ARRAY);
 
 
         // Since we don't have per-vertex color, the implicit color is white.
         // Since we don't have per-vertex color, the implicit color is white.
-        GLPf(Color4)(1.0f, 1.0f, 1.0f, 1.0f);
+        if (_color_scale_via_lighting) {
+          GLPf(Color4)(1.0f, 1.0f, 1.0f, 1.0f);
+        } else {
+          LColor color = _scene_graph_color;
+          color.componentwise_mult(_current_color_scale);
+          GLPf(Color4)(color[0], color[1], color[2], color[3]);
+        }
       }
       }
 
 
     // Now set up each of the active texture coordinate stages--or at least
     // Now set up each of the active texture coordinate stages--or at least

+ 2 - 2
panda/src/glstuff/glShaderContext_src.cxx

@@ -2440,9 +2440,9 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) {
         if (p == _color_attrib_index) {
         if (p == _color_attrib_index) {
           // Vertex colors are disabled or not present.  Apply flat color.
           // Vertex colors are disabled or not present.  Apply flat color.
 #ifdef STDFLOAT_DOUBLE
 #ifdef STDFLOAT_DOUBLE
-          _glgsg->_glVertexAttrib4dv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4dv(p, _glgsg->_scene_graph_color.get_data());
 #else
 #else
-          _glgsg->_glVertexAttrib4fv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4fv(p, _glgsg->_scene_graph_color.get_data());
 #endif
 #endif
         }
         }
       }
       }

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

@@ -84,7 +84,7 @@ operator = (const TextureStage &other) {
   _combine_rgb_operand2 = other._combine_rgb_operand2;
   _combine_rgb_operand2 = other._combine_rgb_operand2;
   _combine_alpha_mode = other._combine_alpha_mode;
   _combine_alpha_mode = other._combine_alpha_mode;
   _combine_alpha_source0 = other._combine_alpha_source0;
   _combine_alpha_source0 = other._combine_alpha_source0;
-  _combine_alpha_operand0 = _combine_alpha_operand0;
+  _combine_alpha_operand0 = other._combine_alpha_operand0;
   _combine_alpha_source1 = other._combine_alpha_source1;
   _combine_alpha_source1 = other._combine_alpha_source1;
   _combine_alpha_operand1 = other._combine_alpha_operand1;
   _combine_alpha_operand1 = other._combine_alpha_operand1;
   _combine_alpha_source2 = other._combine_alpha_source2;
   _combine_alpha_source2 = other._combine_alpha_source2;

+ 10 - 0
panda/src/grutil/shaderTerrainMesh.I

@@ -22,6 +22,7 @@
  * @param filename Heightfield texture
  * @param filename Heightfield texture
  */
  */
 INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
 INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
+  MutexHolder holder(_lock);
   _heightfield_tex = heightfield;
   _heightfield_tex = heightfield;
 }
 }
 
 
@@ -33,6 +34,7 @@ INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
  * @return Path to the heightfield
  * @return Path to the heightfield
  */
  */
 INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
 INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
+  MutexHolder holder(_lock);
   return _heightfield_tex;
   return _heightfield_tex;
 }
 }
 
 
@@ -54,6 +56,7 @@ INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
  * @param chunk_size Size of the chunks, has to be a power of two
  * @param chunk_size Size of the chunks, has to be a power of two
  */
  */
 INLINE void ShaderTerrainMesh::set_chunk_size(size_t chunk_size) {
 INLINE void ShaderTerrainMesh::set_chunk_size(size_t chunk_size) {
+  MutexHolder holder(_lock);
   _chunk_size = chunk_size;
   _chunk_size = chunk_size;
 }
 }
 
 
@@ -63,6 +66,7 @@ INLINE void ShaderTerrainMesh::set_chunk_size(size_t chunk_size) {
  * @return Chunk size
  * @return Chunk size
  */
  */
 INLINE size_t ShaderTerrainMesh::get_chunk_size() const {
 INLINE size_t ShaderTerrainMesh::get_chunk_size() const {
+  MutexHolder holder(_lock);
   return _chunk_size;
   return _chunk_size;
 }
 }
 
 
@@ -81,6 +85,7 @@ INLINE size_t ShaderTerrainMesh::get_chunk_size() const {
  * @param generate_patches [description]
  * @param generate_patches [description]
  */
  */
 INLINE void ShaderTerrainMesh::set_generate_patches(bool generate_patches) {
 INLINE void ShaderTerrainMesh::set_generate_patches(bool generate_patches) {
+  MutexHolder holder(_lock);
   _generate_patches = generate_patches;
   _generate_patches = generate_patches;
 }
 }
 
 
@@ -92,6 +97,7 @@ INLINE void ShaderTerrainMesh::set_generate_patches(bool generate_patches) {
  * @return Whether to generate patches
  * @return Whether to generate patches
  */
  */
 INLINE bool ShaderTerrainMesh::get_generate_patches() const {
 INLINE bool ShaderTerrainMesh::get_generate_patches() const {
+  MutexHolder holder(_lock);
   return _generate_patches;
   return _generate_patches;
 }
 }
 
 
@@ -107,6 +113,7 @@ INLINE bool ShaderTerrainMesh::get_generate_patches() const {
  * @param target_triangle_width Desired triangle width in pixels
  * @param target_triangle_width Desired triangle width in pixels
  */
  */
 INLINE void ShaderTerrainMesh::set_target_triangle_width(PN_stdfloat target_triangle_width) {
 INLINE void ShaderTerrainMesh::set_target_triangle_width(PN_stdfloat target_triangle_width) {
+  MutexHolder holder(_lock);
   _target_triangle_width = target_triangle_width;
   _target_triangle_width = target_triangle_width;
 }
 }
 
 
@@ -118,6 +125,7 @@ INLINE void ShaderTerrainMesh::set_target_triangle_width(PN_stdfloat target_tria
  * @return Target triangle width
  * @return Target triangle width
  */
  */
 INLINE PN_stdfloat ShaderTerrainMesh::get_target_triangle_width() const {
 INLINE PN_stdfloat ShaderTerrainMesh::get_target_triangle_width() const {
+  MutexHolder holder(_lock);
   return _target_triangle_width;
   return _target_triangle_width;
 }
 }
 
 
@@ -131,6 +139,7 @@ INLINE PN_stdfloat ShaderTerrainMesh::get_target_triangle_width() const {
  * @param update_enabled Whether to update the terrain
  * @param update_enabled Whether to update the terrain
  */
  */
 INLINE void ShaderTerrainMesh::set_update_enabled(bool update_enabled) {
 INLINE void ShaderTerrainMesh::set_update_enabled(bool update_enabled) {
+  MutexHolder holder(_lock);
   _update_enabled = update_enabled;
   _update_enabled = update_enabled;
 }
 }
 
 
@@ -142,6 +151,7 @@ INLINE void ShaderTerrainMesh::set_update_enabled(bool update_enabled) {
  * @return Whether to update the terrain
  * @return Whether to update the terrain
  */
  */
 INLINE bool ShaderTerrainMesh::get_update_enabled() const {
 INLINE bool ShaderTerrainMesh::get_update_enabled() const {
+  MutexHolder holder(_lock);
   return _update_enabled;
   return _update_enabled;
 }
 }
 
 

+ 3 - 0
panda/src/grutil/shaderTerrainMesh.cxx

@@ -122,6 +122,7 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
  * @return true if the terrain was initialized, false if an error occured
  * @return true if the terrain was initialized, false if an error occured
  */
  */
 bool ShaderTerrainMesh::generate() {
 bool ShaderTerrainMesh::generate() {
+  MutexHolder holder(_lock);
   if (!do_check_heightfield())
   if (!do_check_heightfield())
     return false;
     return false;
 
 
@@ -461,6 +462,7 @@ bool ShaderTerrainMesh::safe_to_combine() const {
  * @copydoc PandaNode::add_for_draw()
  * @copydoc PandaNode::add_for_draw()
  */
  */
 void ShaderTerrainMesh::add_for_draw(CullTraverser *trav, CullTraverserData &data) {
 void ShaderTerrainMesh::add_for_draw(CullTraverser *trav, CullTraverserData &data) {
+  MutexHolder holder(_lock);
 
 
   // Make sure the terrain was properly initialized, and the geom was created
   // Make sure the terrain was properly initialized, and the geom was created
   // successfully
   // successfully
@@ -711,6 +713,7 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
  * @return World-Space point
  * @return World-Space point
  */
  */
 LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const {
 LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const {
+  MutexHolder holder(_lock);
   nassertr(_heightfield_tex != nullptr, LPoint3(0)); // Heightfield not set yet
   nassertr(_heightfield_tex != nullptr, LPoint3(0)); // Heightfield not set yet
   nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory
   nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory
 
 

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

@@ -25,6 +25,8 @@
 #include "configVariableInt.h"
 #include "configVariableInt.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
 #include "filename.h"
 #include "filename.h"
+#include "pmutex.h"
+#include "mutexHolder.h"
 #include <stdint.h>
 #include <stdint.h>
 
 
 extern ConfigVariableBool stm_use_hexagonal_layout;
 extern ConfigVariableBool stm_use_hexagonal_layout;
@@ -160,6 +162,7 @@ private:
   void do_emit_chunk(Chunk* chunk, TraversalData* data);
   void do_emit_chunk(Chunk* chunk, TraversalData* data);
   bool do_check_lod_matches(Chunk* chunk, TraversalData* data);
   bool do_check_lod_matches(Chunk* chunk, TraversalData* data);
 
 
+  Mutex _lock;
   Chunk _base_chunk;
   Chunk _base_chunk;
   size_t _size;
   size_t _size;
   size_t _chunk_size;
   size_t _chunk_size;

+ 6 - 8
panda/src/mathutil/mersenne.h

@@ -69,14 +69,12 @@ PUBLISHED:
   };
   };
 
 
 private:
 private:
-  enum {
-    // Period parameters
-    N = 624,
-    M = 397,
-    MATRIX_A = 0x9908b0dfUL,   // constant vector a
-    UPPER_MASK = 0x80000000UL, // most significant w-r bits
-    LOWER_MASK = 0x7fffffffUL, // least significant r bits
-  };
+  // Period parameters
+  static const unsigned long N = 624;
+  static const unsigned long M = 397;
+  static const unsigned long MATRIX_A = 0x9908b0dfUL;   // constant vector a
+  static const unsigned long UPPER_MASK = 0x80000000UL; // most significant w-r bits
+  static const unsigned long LOWER_MASK = 0x7fffffffUL; // least significant r bits
 
 
   unsigned long mt[N]; // the array for the state vector
   unsigned long mt[N]; // the array for the state vector
   unsigned int mti; // mti==N+1 means mt[N] is not initialized
   unsigned int mti; // mti==N+1 means mt[N] is not initialized

+ 17 - 0
panda/src/movies/movieTypeRegistry.cxx

@@ -12,10 +12,12 @@
  */
  */
 
 
 #include "movieTypeRegistry.h"
 #include "movieTypeRegistry.h"
+
 #include "string_utils.h"
 #include "string_utils.h"
 #include "config_movies.h"
 #include "config_movies.h"
 #include "config_putil.h"
 #include "config_putil.h"
 #include "load_dso.h"
 #include "load_dso.h"
+#include "reMutexHolder.h"
 
 
 using std::endl;
 using std::endl;
 using std::string;
 using std::string;
@@ -29,6 +31,8 @@ PT(MovieAudio) MovieTypeRegistry::
 make_audio(const Filename &name) {
 make_audio(const Filename &name) {
   string ext = downcase(name.get_extension());
   string ext = downcase(name.get_extension());
 
 
+  _audio_lock.lock();
+
   // Make sure that the list of audio types has been read in.
   // Make sure that the list of audio types has been read in.
   load_audio_types();
   load_audio_types();
 
 
@@ -41,6 +45,7 @@ make_audio(const Filename &name) {
   // Explicit extension is preferred over catch-all.
   // Explicit extension is preferred over catch-all.
   if (_audio_type_registry.count(ext)) {
   if (_audio_type_registry.count(ext)) {
     MakeAudioFunc func = _audio_type_registry[ext];
     MakeAudioFunc func = _audio_type_registry[ext];
+    _audio_lock.unlock();
     return (*func)(name);
     return (*func)(name);
   }
   }
 
 
@@ -53,12 +58,14 @@ make_audio(const Filename &name) {
 
 
   if (_audio_type_registry.count("*")) {
   if (_audio_type_registry.count("*")) {
     MakeAudioFunc func = _audio_type_registry["*"];
     MakeAudioFunc func = _audio_type_registry["*"];
+    _audio_lock.unlock();
     return (*func)(name);
     return (*func)(name);
   }
   }
 
 
   movies_cat.error()
   movies_cat.error()
     << "Support for audio files with extension ." << ext << " was not enabled.\n";
     << "Support for audio files with extension ." << ext << " was not enabled.\n";
 
 
+  _audio_lock.unlock();
   return new MovieAudio("Load-Failure Stub");
   return new MovieAudio("Load-Failure Stub");
 }
 }
 
 
@@ -68,6 +75,7 @@ make_audio(const Filename &name) {
  */
  */
 void MovieTypeRegistry::
 void MovieTypeRegistry::
 register_audio_type(MakeAudioFunc func, const string &extensions) {
 register_audio_type(MakeAudioFunc func, const string &extensions) {
+  ReMutexHolder holder(_audio_lock);
   vector_string words;
   vector_string words;
   extract_words(downcase(extensions), words);
   extract_words(downcase(extensions), words);
 
 
@@ -89,6 +97,7 @@ register_audio_type(MakeAudioFunc func, const string &extensions) {
  */
  */
 void MovieTypeRegistry::
 void MovieTypeRegistry::
 load_audio_types() {
 load_audio_types() {
+  ReMutexHolder holder(_audio_lock);
   static bool audio_types_loaded = false;
   static bool audio_types_loaded = false;
 
 
   if (!audio_types_loaded) {
   if (!audio_types_loaded) {
@@ -145,6 +154,8 @@ PT(MovieVideo) MovieTypeRegistry::
 make_video(const Filename &name) {
 make_video(const Filename &name) {
   string ext = downcase(name.get_extension());
   string ext = downcase(name.get_extension());
 
 
+  _video_lock.lock();
+
   // Make sure that the list of video types has been read in.
   // Make sure that the list of video types has been read in.
   load_video_types();
   load_video_types();
 
 
@@ -157,6 +168,7 @@ make_video(const Filename &name) {
   // Explicit extension is preferred over catch-all.
   // Explicit extension is preferred over catch-all.
   if (_video_type_registry.count(ext)) {
   if (_video_type_registry.count(ext)) {
     MakeVideoFunc func = _video_type_registry[ext];
     MakeVideoFunc func = _video_type_registry[ext];
+    _video_lock.unlock();
     return (*func)(name);
     return (*func)(name);
   }
   }
 
 
@@ -169,12 +181,14 @@ make_video(const Filename &name) {
 
 
   if (_video_type_registry.count("*")) {
   if (_video_type_registry.count("*")) {
     MakeVideoFunc func = _video_type_registry["*"];
     MakeVideoFunc func = _video_type_registry["*"];
+    _video_lock.unlock();
     return (*func)(name);
     return (*func)(name);
   }
   }
 
 
   movies_cat.error()
   movies_cat.error()
     << "Support for video files with extension ." << ext << " was not enabled.\n";
     << "Support for video files with extension ." << ext << " was not enabled.\n";
 
 
+  _video_lock.unlock();
   return new MovieVideo("Load-Failure Stub");
   return new MovieVideo("Load-Failure Stub");
 }
 }
 
 
@@ -184,6 +198,7 @@ make_video(const Filename &name) {
  */
  */
 void MovieTypeRegistry::
 void MovieTypeRegistry::
 register_video_type(MakeVideoFunc func, const string &extensions) {
 register_video_type(MakeVideoFunc func, const string &extensions) {
+  ReMutexHolder holder(_video_lock);
   vector_string words;
   vector_string words;
   extract_words(downcase(extensions), words);
   extract_words(downcase(extensions), words);
 
 
@@ -205,6 +220,7 @@ register_video_type(MakeVideoFunc func, const string &extensions) {
  */
  */
 void MovieTypeRegistry::
 void MovieTypeRegistry::
 load_video_types() {
 load_video_types() {
+  ReMutexHolder holder(_video_lock);
   static bool video_types_loaded = false;
   static bool video_types_loaded = false;
 
 
   if (!video_types_loaded) {
   if (!video_types_loaded) {
@@ -259,6 +275,7 @@ load_video_types() {
  */
  */
 void MovieTypeRegistry::
 void MovieTypeRegistry::
 load_movie_library(const string &name) {
 load_movie_library(const string &name) {
+  ReMutexHolder holder(_video_lock);
   Filename dlname = Filename::dso_filename("lib" + name + ".so");
   Filename dlname = Filename::dso_filename("lib" + name + ".so");
   movies_cat.info()
   movies_cat.info()
     << "loading video type module: " << name << endl;
     << "loading video type module: " << name << endl;

+ 3 - 0
panda/src/movies/movieTypeRegistry.h

@@ -19,6 +19,7 @@
 #include "movieVideo.h"
 #include "movieVideo.h"
 #include "filename.h"
 #include "filename.h"
 #include "pmap.h"
 #include "pmap.h"
+#include "reMutex.h"
 
 
 /**
 /**
  * This class records the different types of MovieAudio and MovieVideo that
  * This class records the different types of MovieAudio and MovieVideo that
@@ -43,9 +44,11 @@ public:
 private:
 private:
   static MovieTypeRegistry *_global_ptr;
   static MovieTypeRegistry *_global_ptr;
 
 
+  ReMutex _audio_lock;
   pmap<std::string, MakeAudioFunc> _audio_type_registry;
   pmap<std::string, MakeAudioFunc> _audio_type_registry;
   pmap<std::string, std::string> _deferred_audio_types;
   pmap<std::string, std::string> _deferred_audio_types;
 
 
+  ReMutex _video_lock;
   pmap<std::string, MakeVideoFunc> _video_type_registry;
   pmap<std::string, MakeVideoFunc> _video_type_registry;
   pmap<std::string, std::string> _deferred_video_types;
   pmap<std::string, std::string> _deferred_video_types;
 };
 };

+ 1 - 1
panda/src/osxdisplay/osxGraphicsStateGuardian.h

@@ -64,7 +64,7 @@ private:
   CGGammaValue _gOriginalRedTable[ 256 ];
   CGGammaValue _gOriginalRedTable[ 256 ];
   CGGammaValue _gOriginalGreenTable[ 256 ];
   CGGammaValue _gOriginalGreenTable[ 256 ];
   CGGammaValue _gOriginalBlueTable[ 256 ];
   CGGammaValue _gOriginalBlueTable[ 256 ];
-  CGTableCount _sampleCount;
+  uint32_t _sampleCount;
   CGDisplayErr _cgErr;
   CGDisplayErr _cgErr;
 
 
 public:
 public:

+ 0 - 1
panda/src/particlesystem/baseParticle.cxx

@@ -68,7 +68,6 @@ write(std::ostream &out, int indent) const {
   out.width(indent+2); out<<""; out<<"_lifespan "<<_lifespan<<"\n";
   out.width(indent+2); out<<""; out<<"_lifespan "<<_lifespan<<"\n";
   out.width(indent+2); out<<""; out<<"_alive "<<_alive<<"\n";
   out.width(indent+2); out<<""; out<<"_alive "<<_alive<<"\n";
   out.width(indent+2); out<<""; out<<"_index "<<_index<<"\n";
   out.width(indent+2); out<<""; out<<"_index "<<_index<<"\n";
-  out.width(indent+2); out<<""; out<<"_last_position "<<_last_position<<"\n";
   PhysicsObject::write(out, indent+2);
   PhysicsObject::write(out, indent+2);
   #endif //] NDEBUG
   #endif //] NDEBUG
 }
 }

+ 0 - 2
panda/src/particlesystem/baseParticle.h

@@ -62,8 +62,6 @@ private:
   PN_stdfloat _lifespan;
   PN_stdfloat _lifespan;
   bool _alive;
   bool _alive;
   int _index;
   int _index;
-
-  LPoint3 _last_position;
 };
 };
 
 
 #include "baseParticle.I"
 #include "baseParticle.I"

+ 6 - 6
panda/src/pgraph/colorAttrib.cxx

@@ -68,7 +68,7 @@ make_off() {
  */
  */
 CPT(RenderAttrib) ColorAttrib::
 CPT(RenderAttrib) ColorAttrib::
 make_default() {
 make_default() {
-  return make_off();
+  return make_vertex();
 }
 }
 
 
 /**
 /**
@@ -133,17 +133,17 @@ get_hash_impl() const {
 }
 }
 
 
 /**
 /**
- * Quantizes the color color to the nearest multiple of 1000, just to prevent
+ * Quantizes the flat color to the nearest multiple of 1024, just to prevent
  * runaway accumulation of only slightly-different ColorAttribs.
  * runaway accumulation of only slightly-different ColorAttribs.
  */
  */
 void ColorAttrib::
 void ColorAttrib::
 quantize_color() {
 quantize_color() {
   switch (_type) {
   switch (_type) {
   case T_flat:
   case T_flat:
-    _color[0] = cfloor(_color[0] * 1000.0f + 0.5f) * 0.001f;
-    _color[1] = cfloor(_color[1] * 1000.0f + 0.5f) * 0.001f;
-    _color[2] = cfloor(_color[2] * 1000.0f + 0.5f) * 0.001f;
-    _color[3] = cfloor(_color[3] * 1000.0f + 0.5f) * 0.001f;
+    _color[0] = cfloor(_color[0] * 1024.0f + 0.5f) / 1024.0f;
+    _color[1] = cfloor(_color[1] * 1024.0f + 0.5f) / 1024.0f;
+    _color[2] = cfloor(_color[2] * 1024.0f + 0.5f) / 1024.0f;
+    _color[3] = cfloor(_color[3] * 1024.0f + 0.5f) / 1024.0f;
     break;
     break;
 
 
   case T_off:
   case T_off:

+ 1 - 1
panda/src/pgraph/colorAttrib.h

@@ -88,7 +88,7 @@ public:
     register_type(_type_handle, "ColorAttrib",
     register_type(_type_handle, "ColorAttrib",
                   RenderAttrib::get_class_type());
                   RenderAttrib::get_class_type());
     _attrib_slot = register_slot(_type_handle, 100,
     _attrib_slot = register_slot(_type_handle, 100,
-      new ColorAttrib(T_off, LColor(1, 1, 1, 1)));
+      new ColorAttrib(T_vertex, LColor::zero()));
   }
   }
   virtual TypeHandle get_type() const {
   virtual TypeHandle get_type() const {
     return get_class_type();
     return get_class_type();

+ 5 - 5
panda/src/pgraph/colorScaleAttrib.cxx

@@ -230,15 +230,15 @@ invert_compose_impl(const RenderAttrib *other) const {
 }
 }
 
 
 /**
 /**
- * Quantizes the color scale to the nearest multiple of 1000, just to prevent
+ * Quantizes the color scale to the nearest multiple of 1024, just to prevent
  * runaway accumulation of only slightly-different ColorScaleAttribs.
  * runaway accumulation of only slightly-different ColorScaleAttribs.
  */
  */
 void ColorScaleAttrib::
 void ColorScaleAttrib::
 quantize_scale() {
 quantize_scale() {
-  _scale[0] = cfloor(_scale[0] * 1000.0f + 0.5f) * 0.001f;
-  _scale[1] = cfloor(_scale[1] * 1000.0f + 0.5f) * 0.001f;
-  _scale[2] = cfloor(_scale[2] * 1000.0f + 0.5f) * 0.001f;
-  _scale[3] = cfloor(_scale[3] * 1000.0f + 0.5f) * 0.001f;
+  _scale[0] = cfloor(_scale[0] * 1024.0f + 0.5f) / 1024.0f;
+  _scale[1] = cfloor(_scale[1] * 1024.0f + 0.5f) / 1024.0f;
+  _scale[2] = cfloor(_scale[2] * 1024.0f + 0.5f) / 1024.0f;
+  _scale[3] = cfloor(_scale[3] * 1024.0f + 0.5f) / 1024.0f;
 }
 }
 
 
 /**
 /**

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

@@ -200,7 +200,7 @@ load_file(const Filename &filename, const LoaderOptions &options) const {
 
 
   if (search) {
   if (search) {
     // Look for the file along the model path.
     // Look for the file along the model path.
-    const ConfigVariableSearchPath &model_path = get_model_path();
+    DSearchPath model_path(get_model_path());
     int num_dirs = model_path.get_num_directories();
     int num_dirs = model_path.get_num_directories();
     for (int i = 0; i < num_dirs; ++i) {
     for (int i = 0; i < num_dirs; ++i) {
       Filename pathname(model_path.get_directory(i), this_filename);
       Filename pathname(model_path.get_directory(i), this_filename);

+ 16 - 7
panda/src/pgraphnodes/shaderGenerator.cxx

@@ -53,14 +53,22 @@ TypeHandle ShaderGenerator::_type_handle;
 
 
 #ifdef HAVE_CG
 #ifdef HAVE_CG
 
 
-#define PACK_COMBINE(src0, op0, src1, op1, src2, op2) ( \
-  ((uint16_t)src0) | ((((uint16_t)op0 - 1u) & 3u) << 3u) | \
-  ((uint16_t)src1 << 5u) | ((((uint16_t)op1 - 1u) & 3u) << 8u) | \
-  ((uint16_t)src2 << 10u) | ((((uint16_t)op2 - 1u) & 3u) << 13u))
-
 #define UNPACK_COMBINE_SRC(from, n) (TextureStage::CombineSource)((from >> ((uint16_t)n * 5u)) & 7u)
 #define UNPACK_COMBINE_SRC(from, n) (TextureStage::CombineSource)((from >> ((uint16_t)n * 5u)) & 7u)
 #define UNPACK_COMBINE_OP(from, n) (TextureStage::CombineOperand)(((from >> (((uint16_t)n * 5u) + 3u)) & 3u) + 1u)
 #define UNPACK_COMBINE_OP(from, n) (TextureStage::CombineOperand)(((from >> (((uint16_t)n * 5u) + 3u)) & 3u) + 1u)
 
 
+static inline uint16_t
+pack_combine(TextureStage::CombineSource src0, TextureStage::CombineOperand op0,
+             TextureStage::CombineSource src1, TextureStage::CombineOperand op1,
+             TextureStage::CombineSource src2, TextureStage::CombineOperand op2) {
+  if (op0 == TextureStage::CO_undefined) op0 = TextureStage::CO_src_alpha;
+  if (op1 == TextureStage::CO_undefined) op1 = TextureStage::CO_src_alpha;
+  if (op2 == TextureStage::CO_undefined) op2 = TextureStage::CO_src_alpha;
+
+  return ((uint16_t)src0) | ((((uint16_t)op0 - 1u) & 3u) << 3u) |
+         ((uint16_t)src1 << 5u) | ((((uint16_t)op1 - 1u) & 3u) << 8u) |
+         ((uint16_t)src2 << 10u) | ((((uint16_t)op2 - 1u) & 3u) << 13u);
+}
+
 static PStatCollector lookup_collector("*:Munge:ShaderGen:Lookup");
 static PStatCollector lookup_collector("*:Munge:ShaderGen:Lookup");
 static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
 static PStatCollector synthesize_collector("*:Munge:ShaderGen:Synthesize");
 
 
@@ -399,11 +407,12 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
       if (stage->get_alpha_scale() == 4) {
       if (stage->get_alpha_scale() == 4) {
         info._flags |= ShaderKey::TF_alpha_scale_4;
         info._flags |= ShaderKey::TF_alpha_scale_4;
       }
       }
-      info._combine_rgb = PACK_COMBINE(
+
+      info._combine_rgb = pack_combine(
         stage->get_combine_rgb_source0(), stage->get_combine_rgb_operand0(),
         stage->get_combine_rgb_source0(), stage->get_combine_rgb_operand0(),
         stage->get_combine_rgb_source1(), stage->get_combine_rgb_operand1(),
         stage->get_combine_rgb_source1(), stage->get_combine_rgb_operand1(),
         stage->get_combine_rgb_source2(), stage->get_combine_rgb_operand2());
         stage->get_combine_rgb_source2(), stage->get_combine_rgb_operand2());
-      info._combine_alpha = PACK_COMBINE(
+      info._combine_alpha = pack_combine(
         stage->get_combine_alpha_source0(), stage->get_combine_alpha_operand0(),
         stage->get_combine_alpha_source0(), stage->get_combine_alpha_operand0(),
         stage->get_combine_alpha_source1(), stage->get_combine_alpha_operand1(),
         stage->get_combine_alpha_source1(), stage->get_combine_alpha_operand1(),
         stage->get_combine_alpha_source2(), stage->get_combine_alpha_operand2());
         stage->get_combine_alpha_source2(), stage->get_combine_alpha_operand2());

+ 2 - 0
panda/src/pipeline/conditionVarSpinlockImpl.cxx

@@ -58,4 +58,6 @@ wait(double timeout) {
   _mutex.lock();
   _mutex.lock();
 }
 }
 
 
+#undef PAUSE
+
 #endif  // MUTEX_SPINLOCK
 #endif  // MUTEX_SPINLOCK

+ 2 - 0
panda/src/pipeline/reMutexSpinlockImpl.cxx

@@ -54,4 +54,6 @@ try_lock() {
   }
   }
 }
 }
 
 
+#undef PAUSE
+
 #endif  // MUTEX_SPINLOCK
 #endif  // MUTEX_SPINLOCK

+ 133 - 100
panda/src/text/textNode.I

@@ -33,6 +33,7 @@ get_line_height() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_max_rows(int max_rows) {
 set_max_rows(int max_rows) {
+  MutexHolder holder(_lock);
   _max_rows = max_rows;
   _max_rows = max_rows;
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -43,6 +44,7 @@ set_max_rows(int max_rows) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_max_rows() {
 clear_max_rows() {
+  MutexHolder holder(_lock);
   _max_rows = 0;
   _max_rows = 0;
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -53,6 +55,7 @@ clear_max_rows() {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_max_rows() const {
 has_max_rows() const {
+  MutexHolder holder(_lock);
   return _max_rows > 0;
   return _max_rows > 0;
 }
 }
 
 
@@ -62,6 +65,7 @@ has_max_rows() const {
  */
  */
 INLINE int TextNode::
 INLINE int TextNode::
 get_max_rows() const {
 get_max_rows() const {
+  MutexHolder holder(_lock);
   return _max_rows;
   return _max_rows;
 }
 }
 
 
@@ -71,6 +75,7 @@ get_max_rows() const {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_overflow() const {
 has_overflow() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return (_flags & F_has_overflow) != 0;
   return (_flags & F_has_overflow) != 0;
 }
 }
@@ -88,6 +93,7 @@ set_frame_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_frame_color(const LColor &frame_color) {
 set_frame_color(const LColor &frame_color) {
+  MutexHolder holder(_lock);
   if (_frame_color != frame_color) {
   if (_frame_color != frame_color) {
     _frame_color = frame_color;
     _frame_color = frame_color;
     invalidate_no_measure();
     invalidate_no_measure();
@@ -99,6 +105,7 @@ set_frame_color(const LColor &frame_color) {
  */
  */
 INLINE LColor TextNode::
 INLINE LColor TextNode::
 get_frame_color() const {
 get_frame_color() const {
+  MutexHolder holder(_lock);
   return _frame_color;
   return _frame_color;
 }
 }
 
 
@@ -107,7 +114,8 @@ get_frame_color() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_card_border(PN_stdfloat size, PN_stdfloat uv_portion) {
 set_card_border(PN_stdfloat size, PN_stdfloat uv_portion) {
-  if (!has_card_border() || _card_border_size != size || _card_border_uv_portion != uv_portion) {
+  MutexHolder holder(_lock);
+  if ((_flags & F_has_card_border) == 0 || _card_border_size != size || _card_border_uv_portion != uv_portion) {
     _flags |= F_has_card_border;
     _flags |= F_has_card_border;
     _card_border_size = size;
     _card_border_size = size;
     _card_border_uv_portion = uv_portion;
     _card_border_uv_portion = uv_portion;
@@ -120,7 +128,8 @@ set_card_border(PN_stdfloat size, PN_stdfloat uv_portion) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_card_border() {
 clear_card_border() {
-  if (has_card_border()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_card_border) {
     _flags &= ~F_has_card_border;
     _flags &= ~F_has_card_border;
     invalidate_no_measure();
     invalidate_no_measure();
   }
   }
@@ -131,6 +140,7 @@ clear_card_border() {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_card_border_size() const {
 get_card_border_size() const {
+  MutexHolder holder(_lock);
   return _card_border_size;
   return _card_border_size;
 }
 }
 
 
@@ -139,6 +149,7 @@ get_card_border_size() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_card_border_uv_portion() const {
 get_card_border_uv_portion() const {
+  MutexHolder holder(_lock);
   return _card_border_uv_portion;
   return _card_border_uv_portion;
 }
 }
 
 
@@ -147,6 +158,7 @@ get_card_border_uv_portion() const {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_card_border() const {
 has_card_border() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_card_border) != 0;
   return (_flags & F_has_card_border) != 0;
 }
 }
 
 
@@ -163,6 +175,7 @@ set_card_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_card_color(const LColor &card_color) {
 set_card_color(const LColor &card_color) {
+  MutexHolder holder(_lock);
   if (_card_color != card_color) {
   if (_card_color != card_color) {
     _card_color = card_color;
     _card_color = card_color;
     invalidate_no_measure();
     invalidate_no_measure();
@@ -174,6 +187,7 @@ set_card_color(const LColor &card_color) {
  */
  */
 INLINE LColor TextNode::
 INLINE LColor TextNode::
 get_card_color() const {
 get_card_color() const {
+  MutexHolder holder(_lock);
   return _card_color;
   return _card_color;
 }
 }
 
 
@@ -185,7 +199,8 @@ set_card_texture(Texture *card_texture) {
   if (card_texture == nullptr) {
   if (card_texture == nullptr) {
     clear_card_texture();
     clear_card_texture();
   } else {
   } else {
-    if (!has_card_texture() || _card_texture != card_texture) {
+    MutexHolder holder(_lock);
+    if ((_flags & F_has_card_texture) == 0 || _card_texture != card_texture) {
       _flags |= F_has_card_texture;
       _flags |= F_has_card_texture;
       _card_texture = card_texture;
       _card_texture = card_texture;
       invalidate_no_measure();
       invalidate_no_measure();
@@ -198,7 +213,8 @@ set_card_texture(Texture *card_texture) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_card_texture() {
 clear_card_texture() {
-  if (has_card_texture()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_card_texture) {
     _flags &= ~F_has_card_texture;
     _flags &= ~F_has_card_texture;
     _card_texture = nullptr;
     _card_texture = nullptr;
     invalidate_no_measure();
     invalidate_no_measure();
@@ -210,6 +226,7 @@ clear_card_texture() {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_card_texture() const {
 has_card_texture() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_card_texture) != 0;
   return (_flags & F_has_card_texture) != 0;
 }
 }
 
 
@@ -218,6 +235,7 @@ has_card_texture() const {
  */
  */
 INLINE Texture *TextNode::
 INLINE Texture *TextNode::
 get_card_texture() const {
 get_card_texture() const {
+  MutexHolder holder(_lock);
   return _card_texture;
   return _card_texture;
 }
 }
 
 
@@ -229,6 +247,7 @@ get_card_texture() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_frame_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
 set_frame_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= (F_has_frame | F_frame_as_margin);
   _flags |= (F_has_frame | F_frame_as_margin);
   _frame_ul.set(left, top);
   _frame_ul.set(left, top);
   _frame_lr.set(right, bottom);
   _frame_lr.set(right, bottom);
@@ -243,6 +262,7 @@ set_frame_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_frame_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
 set_frame_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= F_has_frame;
   _flags |= F_has_frame;
   _flags &= ~F_frame_as_margin;
   _flags &= ~F_frame_as_margin;
   _frame_ul.set(left, top);
   _frame_ul.set(left, top);
@@ -255,6 +275,7 @@ set_frame_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_std
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_frame() {
 clear_frame() {
+  MutexHolder holder(_lock);
   _flags &= ~F_has_frame;
   _flags &= ~F_has_frame;
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -264,6 +285,7 @@ clear_frame() {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_frame() const {
 has_frame() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_frame) != 0;
   return (_flags & F_has_frame) != 0;
 }
 }
 
 
@@ -276,7 +298,8 @@ has_frame() const {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 is_frame_as_margin() const {
 is_frame_as_margin() const {
-  nassertr(has_frame(), false);
+  MutexHolder holder(_lock);
+  nassertr((_flags & F_has_frame) != 0, false);
   return (_flags & F_frame_as_margin) != 0;
   return (_flags & F_frame_as_margin) != 0;
 }
 }
 
 
@@ -288,7 +311,8 @@ is_frame_as_margin() const {
  */
  */
 INLINE LVecBase4 TextNode::
 INLINE LVecBase4 TextNode::
 get_frame_as_set() const {
 get_frame_as_set() const {
-  nassertr(has_frame(), LVecBase4(0.0, 0.0, 0.0, 0.0));
+  MutexHolder holder(_lock);
+  nassertr((_flags & F_has_frame) != 0, LVecBase4(0.0, 0.0, 0.0, 0.0));
   return LVecBase4(_frame_ul[0], _frame_lr[0], _frame_lr[1], _frame_ul[1]);
   return LVecBase4(_frame_ul[0], _frame_lr[0], _frame_lr[1], _frame_ul[1]);
 }
 }
 
 
@@ -303,18 +327,20 @@ get_frame_as_set() const {
  */
  */
 INLINE LVecBase4 TextNode::
 INLINE LVecBase4 TextNode::
 get_frame_actual() const {
 get_frame_actual() const {
-  if (!has_frame()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_frame) {
+    if (_flags & F_frame_as_margin) {
+      check_measure();
+      return LVecBase4(_text_ul[0] - _frame_ul[0],
+                       _text_lr[0] + _frame_lr[0],
+                       _text_lr[1] - _frame_lr[1],
+                       _text_ul[1] + _frame_ul[1]);
+    } else {
+      return LVecBase4(_frame_ul[0], _frame_lr[0], _frame_lr[1], _frame_ul[1]);
+    }
+  } else {
     check_measure();
     check_measure();
     return LVecBase4(_text_ul[0], _text_lr[0], _text_lr[1], _text_ul[1]);
     return LVecBase4(_text_ul[0], _text_lr[0], _text_lr[1], _text_ul[1]);
-
-  } else if (is_frame_as_margin()) {
-    check_measure();
-    return LVecBase4(_text_ul[0] - _frame_ul[0],
-                      _text_lr[0] + _frame_lr[0],
-                      _text_lr[1] - _frame_lr[1],
-                      _text_ul[1] + _frame_ul[1]);
-  } else {
-    return get_frame_as_set();
   }
   }
 }
 }
 
 
@@ -323,6 +349,7 @@ get_frame_actual() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_frame_line_width(PN_stdfloat frame_width) {
 set_frame_line_width(PN_stdfloat frame_width) {
+  MutexHolder holder(_lock);
   _frame_width = frame_width;
   _frame_width = frame_width;
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -332,6 +359,7 @@ set_frame_line_width(PN_stdfloat frame_width) {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_frame_line_width() const {
 get_frame_line_width() const {
+  MutexHolder holder(_lock);
   return _frame_width;
   return _frame_width;
 }
 }
 
 
@@ -342,6 +370,7 @@ get_frame_line_width() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_frame_corners(bool corners) {
 set_frame_corners(bool corners) {
+  MutexHolder holder(_lock);
   if (corners) {
   if (corners) {
     _flags |= F_frame_corners;
     _flags |= F_frame_corners;
   } else {
   } else {
@@ -355,6 +384,7 @@ set_frame_corners(bool corners) {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 get_frame_corners() const {
 get_frame_corners() const {
+  MutexHolder holder(_lock);
   return (_flags & F_frame_corners) != 0;
   return (_flags & F_frame_corners) != 0;
 }
 }
 
 
@@ -366,6 +396,7 @@ get_frame_corners() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_card_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
 set_card_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= (F_has_card | F_card_as_margin);
   _flags |= (F_has_card | F_card_as_margin);
   _card_ul.set(left, top);
   _card_ul.set(left, top);
   _card_lr.set(right, bottom);
   _card_lr.set(right, bottom);
@@ -380,6 +411,7 @@ set_card_as_margin(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_s
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_card_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
 set_card_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= F_has_card;
   _flags |= F_has_card;
   _flags &= ~F_card_as_margin;
   _flags &= ~F_card_as_margin;
   _card_ul.set(left, top);
   _card_ul.set(left, top);
@@ -394,6 +426,7 @@ set_card_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdf
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_card_decal(bool card_decal) {
 set_card_decal(bool card_decal) {
+  MutexHolder holder(_lock);
   if (card_decal) {
   if (card_decal) {
     _flags |= F_card_decal;
     _flags |= F_card_decal;
   } else {
   } else {
@@ -407,6 +440,7 @@ set_card_decal(bool card_decal) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_card() {
 clear_card() {
+  MutexHolder holder(_lock);
   _flags &= ~F_has_card;
   _flags &= ~F_has_card;
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -416,6 +450,7 @@ clear_card() {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 has_card() const {
 has_card() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_card) != 0;
   return (_flags & F_has_card) != 0;
 }
 }
 
 
@@ -424,6 +459,7 @@ has_card() const {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 get_card_decal() const {
 get_card_decal() const {
+  MutexHolder holder(_lock);
   return (_flags & F_card_decal) != 0;
   return (_flags & F_card_decal) != 0;
 }
 }
 
 
@@ -436,7 +472,8 @@ get_card_decal() const {
  */
  */
 INLINE bool TextNode::
 INLINE bool TextNode::
 is_card_as_margin() const {
 is_card_as_margin() const {
-  nassertr(has_card(), false);
+  MutexHolder holder(_lock);
+  nassertr((_flags & F_has_card) != 0, false);
   return (_flags & F_card_as_margin) != 0;
   return (_flags & F_card_as_margin) != 0;
 }
 }
 
 
@@ -448,7 +485,8 @@ is_card_as_margin() const {
  */
  */
 INLINE LVecBase4 TextNode::
 INLINE LVecBase4 TextNode::
 get_card_as_set() const {
 get_card_as_set() const {
-  nassertr(has_card(), LVecBase4(0.0, 0.0, 0.0, 0.0));
+  MutexHolder holder(_lock);
+  nassertr((_flags & F_has_card) != 0, LVecBase4(0.0, 0.0, 0.0, 0.0));
   return LVecBase4(_card_ul[0], _card_lr[0], _card_lr[1], _card_ul[1]);
   return LVecBase4(_card_ul[0], _card_lr[0], _card_lr[1], _card_ul[1]);
 }
 }
 
 
@@ -463,18 +501,20 @@ get_card_as_set() const {
  */
  */
 INLINE LVecBase4 TextNode::
 INLINE LVecBase4 TextNode::
 get_card_actual() const {
 get_card_actual() const {
-  if (!has_card()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_card) {
+    if (_flags & F_card_as_margin) {
+      check_measure();
+      return LVecBase4(_text_ul[0] - _card_ul[0],
+                       _text_lr[0] + _card_lr[0],
+                       _text_lr[1] - _card_lr[1],
+                       _text_ul[1] + _card_ul[1]);
+    } else {
+      return LVecBase4(_card_ul[0], _card_lr[0], _card_lr[1], _card_ul[1]);
+    }
+  } else {
     check_measure();
     check_measure();
     return LVecBase4(_text_ul[0], _text_lr[0], _text_lr[1], _text_ul[1]);
     return LVecBase4(_text_ul[0], _text_lr[0], _text_lr[1], _text_ul[1]);
-
-  } else if (is_card_as_margin()) {
-    check_measure();
-    return LVecBase4(_text_ul[0] - _card_ul[0],
-                      _text_lr[0] + _card_lr[0],
-                      _text_lr[1] - _card_lr[1],
-                      _text_ul[1] + _card_ul[1]);
-  } else {
-    return get_card_as_set();
   }
   }
 }
 }
 
 
@@ -487,6 +527,8 @@ get_card_actual() const {
 INLINE LVecBase4 TextNode::
 INLINE LVecBase4 TextNode::
 get_card_transformed() const {
 get_card_transformed() const {
   LVecBase4 card = get_card_actual();
   LVecBase4 card = get_card_actual();
+
+  MutexHolder holder(_lock);
   LPoint3 ul = LPoint3(card[0], 0.0, card[3]) * _transform;
   LPoint3 ul = LPoint3(card[0], 0.0, card[3]) * _transform;
   LPoint3 lr = LPoint3(card[1], 0.0, card[2]) * _transform;
   LPoint3 lr = LPoint3(card[1], 0.0, card[2]) * _transform;
 
 
@@ -498,6 +540,7 @@ get_card_transformed() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_transform(const LMatrix4 &transform) {
 set_transform(const LMatrix4 &transform) {
+  MutexHolder holder(_lock);
   _transform = transform;
   _transform = transform;
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -507,6 +550,7 @@ set_transform(const LMatrix4 &transform) {
  */
  */
 INLINE LMatrix4 TextNode::
 INLINE LMatrix4 TextNode::
 get_transform() const {
 get_transform() const {
+  MutexHolder holder(_lock);
   return _transform;
   return _transform;
 }
 }
 
 
@@ -515,6 +559,7 @@ get_transform() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_coordinate_system(CoordinateSystem coordinate_system) {
 set_coordinate_system(CoordinateSystem coordinate_system) {
+  MutexHolder holder(_lock);
   _coordinate_system = coordinate_system;
   _coordinate_system = coordinate_system;
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -524,6 +569,7 @@ set_coordinate_system(CoordinateSystem coordinate_system) {
  */
  */
 INLINE CoordinateSystem TextNode::
 INLINE CoordinateSystem TextNode::
 get_coordinate_system() const {
 get_coordinate_system() const {
+  MutexHolder holder(_lock);
   return _coordinate_system;
   return _coordinate_system;
 }
 }
 
 
@@ -535,6 +581,7 @@ get_coordinate_system() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_usage_hint(Geom::UsageHint usage_hint) {
 set_usage_hint(Geom::UsageHint usage_hint) {
+  MutexHolder holder(_lock);
   _usage_hint = usage_hint;
   _usage_hint = usage_hint;
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -545,6 +592,7 @@ set_usage_hint(Geom::UsageHint usage_hint) {
  */
  */
 INLINE Geom::UsageHint TextNode::
 INLINE Geom::UsageHint TextNode::
 get_usage_hint() const {
 get_usage_hint() const {
+  MutexHolder holder(_lock);
   return _usage_hint;
   return _usage_hint;
 }
 }
 
 
@@ -585,6 +633,7 @@ get_usage_hint() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_flatten_flags(int flatten_flags) {
 set_flatten_flags(int flatten_flags) {
+  MutexHolder holder(_lock);
   _flatten_flags = flatten_flags;
   _flatten_flags = flatten_flags;
 }
 }
 
 
@@ -593,6 +642,7 @@ set_flatten_flags(int flatten_flags) {
  */
  */
 INLINE int TextNode::
 INLINE int TextNode::
 get_flatten_flags() const {
 get_flatten_flags() const {
+  MutexHolder holder(_lock);
   return _flatten_flags;
   return _flatten_flags;
 }
 }
 
 
@@ -602,6 +652,7 @@ get_flatten_flags() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_font(TextFont *font) {
 set_font(TextFont *font) {
+  MutexHolder holder(_lock);
   TextProperties::set_font(font);
   TextProperties::set_font(font);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -611,6 +662,7 @@ set_font(TextFont *font) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_font() {
 clear_font() {
+  MutexHolder holder(_lock);
   TextProperties::clear_font();
   TextProperties::clear_font();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -631,6 +683,7 @@ clear_font() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_small_caps(bool small_caps) {
 set_small_caps(bool small_caps) {
+  MutexHolder holder(_lock);
   TextProperties::set_small_caps(small_caps);
   TextProperties::set_small_caps(small_caps);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -640,6 +693,7 @@ set_small_caps(bool small_caps) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_small_caps() {
 clear_small_caps() {
+  MutexHolder holder(_lock);
   TextProperties::clear_small_caps();
   TextProperties::clear_small_caps();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -651,6 +705,7 @@ clear_small_caps() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_small_caps_scale(PN_stdfloat small_caps_scale) {
 set_small_caps_scale(PN_stdfloat small_caps_scale) {
+  MutexHolder holder(_lock);
   TextProperties::set_small_caps_scale(small_caps_scale);
   TextProperties::set_small_caps_scale(small_caps_scale);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -660,6 +715,7 @@ set_small_caps_scale(PN_stdfloat small_caps_scale) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_small_caps_scale() {
 clear_small_caps_scale() {
+  MutexHolder holder(_lock);
   TextProperties::clear_small_caps_scale();
   TextProperties::clear_small_caps_scale();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -669,6 +725,7 @@ clear_small_caps_scale() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_slant(PN_stdfloat slant) {
 set_slant(PN_stdfloat slant) {
+  MutexHolder holder(_lock);
   TextProperties::set_slant(slant);
   TextProperties::set_slant(slant);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -678,6 +735,7 @@ set_slant(PN_stdfloat slant) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_slant() {
 clear_slant() {
+  MutexHolder holder(_lock);
   TextProperties::clear_slant();
   TextProperties::clear_slant();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -687,6 +745,7 @@ clear_slant() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_align(TextNode::Alignment align_type) {
 set_align(TextNode::Alignment align_type) {
+  MutexHolder holder(_lock);
   TextProperties::set_align(align_type);
   TextProperties::set_align(align_type);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -696,6 +755,7 @@ set_align(TextNode::Alignment align_type) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_align() {
 clear_align() {
+  MutexHolder holder(_lock);
   TextProperties::clear_align();
   TextProperties::clear_align();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -706,6 +766,7 @@ clear_align() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_indent(PN_stdfloat indent) {
 set_indent(PN_stdfloat indent) {
+  MutexHolder holder(_lock);
   TextProperties::set_indent(indent);
   TextProperties::set_indent(indent);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -715,6 +776,7 @@ set_indent(PN_stdfloat indent) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_indent() {
 clear_indent() {
+  MutexHolder holder(_lock);
   TextProperties::clear_indent();
   TextProperties::clear_indent();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -725,6 +787,7 @@ clear_indent() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_wordwrap(PN_stdfloat wordwrap) {
 set_wordwrap(PN_stdfloat wordwrap) {
+  MutexHolder holder(_lock);
   TextProperties::set_wordwrap(wordwrap);
   TextProperties::set_wordwrap(wordwrap);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -735,6 +798,7 @@ set_wordwrap(PN_stdfloat wordwrap) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_wordwrap() {
 clear_wordwrap() {
+  MutexHolder holder(_lock);
   TextProperties::clear_wordwrap();
   TextProperties::clear_wordwrap();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -744,6 +808,7 @@ clear_wordwrap() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_text_color(const LColor &text_color) {
 set_text_color(const LColor &text_color) {
+  MutexHolder holder(_lock);
   TextProperties::set_text_color(text_color);
   TextProperties::set_text_color(text_color);
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -762,6 +827,7 @@ set_text_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_text_color() {
 clear_text_color() {
+  MutexHolder holder(_lock);
   TextProperties::clear_text_color();
   TextProperties::clear_text_color();
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -779,6 +845,7 @@ set_shadow_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_shadow_color(const LColor &shadow_color) {
 set_shadow_color(const LColor &shadow_color) {
+  MutexHolder holder(_lock);
   TextProperties::set_shadow_color(shadow_color);
   TextProperties::set_shadow_color(shadow_color);
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -788,6 +855,7 @@ set_shadow_color(const LColor &shadow_color) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_shadow_color() {
 clear_shadow_color() {
+  MutexHolder holder(_lock);
   TextProperties::clear_shadow_color();
   TextProperties::clear_shadow_color();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -807,6 +875,7 @@ set_shadow(PN_stdfloat xoffset, PN_stdfloat yoffset) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_shadow(const LVecBase2 &shadow_offset) {
 set_shadow(const LVecBase2 &shadow_offset) {
+  MutexHolder holder(_lock);
   TextProperties::set_shadow(shadow_offset);
   TextProperties::set_shadow(shadow_offset);
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -816,6 +885,7 @@ set_shadow(const LVecBase2 &shadow_offset) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_shadow() {
 clear_shadow() {
+  MutexHolder holder(_lock);
   TextProperties::clear_shadow();
   TextProperties::clear_shadow();
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -831,6 +901,7 @@ clear_shadow() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_bin(const std::string &bin) {
 set_bin(const std::string &bin) {
+  MutexHolder holder(_lock);
   TextProperties::set_bin(bin);
   TextProperties::set_bin(bin);
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -841,6 +912,7 @@ set_bin(const std::string &bin) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_bin() {
 clear_bin() {
+  MutexHolder holder(_lock);
   TextProperties::clear_bin();
   TextProperties::clear_bin();
   invalidate_no_measure();
   invalidate_no_measure();
 }
 }
@@ -858,6 +930,7 @@ clear_bin() {
  */
  */
 INLINE int TextNode::
 INLINE int TextNode::
 set_draw_order(int draw_order) {
 set_draw_order(int draw_order) {
+  MutexHolder holder(_lock);
   invalidate_no_measure();
   invalidate_no_measure();
   return TextProperties::set_draw_order(draw_order);
   return TextProperties::set_draw_order(draw_order);
 }
 }
@@ -867,6 +940,7 @@ set_draw_order(int draw_order) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_draw_order() {
 clear_draw_order() {
+  MutexHolder holder(_lock);
   TextProperties::clear_draw_order();
   TextProperties::clear_draw_order();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -877,6 +951,7 @@ clear_draw_order() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_tab_width(PN_stdfloat tab_width) {
 set_tab_width(PN_stdfloat tab_width) {
+  MutexHolder holder(_lock);
   TextProperties::set_tab_width(tab_width);
   TextProperties::set_tab_width(tab_width);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -886,6 +961,7 @@ set_tab_width(PN_stdfloat tab_width) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_tab_width() {
 clear_tab_width() {
+  MutexHolder holder(_lock);
   TextProperties::clear_tab_width();
   TextProperties::clear_tab_width();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -897,6 +973,7 @@ clear_tab_width() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_glyph_scale(PN_stdfloat glyph_scale) {
 set_glyph_scale(PN_stdfloat glyph_scale) {
+  MutexHolder holder(_lock);
   TextProperties::set_glyph_scale(glyph_scale);
   TextProperties::set_glyph_scale(glyph_scale);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -906,6 +983,7 @@ set_glyph_scale(PN_stdfloat glyph_scale) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_glyph_scale() {
 clear_glyph_scale() {
+  MutexHolder holder(_lock);
   TextProperties::clear_glyph_scale();
   TextProperties::clear_glyph_scale();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -917,6 +995,7 @@ clear_glyph_scale() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 set_glyph_shift(PN_stdfloat glyph_shift) {
 set_glyph_shift(PN_stdfloat glyph_shift) {
+  MutexHolder holder(_lock);
   TextProperties::set_glyph_shift(glyph_shift);
   TextProperties::set_glyph_shift(glyph_shift);
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
@@ -926,60 +1005,11 @@ set_glyph_shift(PN_stdfloat glyph_shift) {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 clear_glyph_shift() {
 clear_glyph_shift() {
+  MutexHolder holder(_lock);
   TextProperties::clear_glyph_shift();
   TextProperties::clear_glyph_shift();
   invalidate_with_measure();
   invalidate_with_measure();
 }
 }
 
 
-
-/**
- * Changes the text that is displayed under the TextNode.
- */
-INLINE void TextNode::
-set_text(const std::string &text) {
-  TextEncoder::set_text(text);
-  invalidate_with_measure();
-}
-
-/**
- * The two-parameter version of set_text() accepts an explicit encoding; the
- * text is immediately decoded and stored as a wide-character string.
- * Subsequent calls to get_text() will return the same text re-encoded using
- * whichever encoding is specified by set_encoding().
- */
-INLINE void TextNode::
-set_text(const std::string &text, TextNode::Encoding encoding) {
-  TextEncoder::set_text(text, encoding);
-  invalidate_with_measure();
-}
-
-/**
- * Removes the text from the TextNode.
- */
-INLINE void TextNode::
-clear_text() {
-  TextEncoder::clear_text();
-  invalidate_with_measure();
-}
-
-/**
- * Appends the indicates string to the end of the stored text.
- */
-INLINE void TextNode::
-append_text(const std::string &text) {
-  TextEncoder::append_text(text);
-  invalidate_with_measure();
-}
-
-/**
- * Appends a single character to the end of the stored text.  This may be a
- * wide character, up to 16 bits in Unicode.
- */
-INLINE void TextNode::
-append_unicode_char(wchar_t character) {
-  TextEncoder::append_unicode_char(character);
-  invalidate_with_measure();
-}
-
 /**
 /**
  * Returns a string that represents the contents of the text, as it has been
  * Returns a string that represents the contents of the text, as it has been
  * formatted by wordwrap rules.
  * formatted by wordwrap rules.
@@ -1001,26 +1031,6 @@ calc_width(const std::string &line) const {
   return calc_width(decode_text(line));
   return calc_width(decode_text(line));
 }
 }
 
 
-/**
- * Changes the text that is displayed under the TextNode, with a wide text.
- * This automatically sets the string reported by get_text() to the 8-bit
- * encoded version of the same string.
- */
-INLINE void TextNode::
-set_wtext(const std::wstring &wtext) {
-  TextEncoder::set_wtext(wtext);
-  invalidate_with_measure();
-}
-
-/**
- * Appends the indicates string to the end of the stored wide-character text.
- */
-INLINE void TextNode::
-append_wtext(const std::wstring &wtext) {
-  TextEncoder::append_wtext(wtext);
-  invalidate_with_measure();
-}
-
 /**
 /**
  * Returns a wstring that represents the contents of the text, as it has been
  * Returns a wstring that represents the contents of the text, as it has been
  * formatted by wordwrap rules.
  * formatted by wordwrap rules.
@@ -1030,6 +1040,7 @@ append_wtext(const std::wstring &wtext) {
  */
  */
 INLINE std::wstring TextNode::
 INLINE std::wstring TextNode::
 get_wordwrapped_wtext() const {
 get_wordwrapped_wtext() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _wordwrapped_wtext;
   return _wordwrapped_wtext;
 }
 }
@@ -1040,6 +1051,7 @@ get_wordwrapped_wtext() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_left() const {
 get_left() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_ul[0];
   return _text_ul[0];
 }
 }
@@ -1050,6 +1062,7 @@ get_left() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_right() const {
 get_right() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_lr[0];
   return _text_lr[0];
 }
 }
@@ -1060,6 +1073,7 @@ get_right() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_bottom() const {
 get_bottom() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_lr[1];
   return _text_lr[1];
 }
 }
@@ -1070,6 +1084,7 @@ get_bottom() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_top() const {
 get_top() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_ul[1];
   return _text_ul[1];
 }
 }
@@ -1079,6 +1094,7 @@ get_top() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_height() const {
 get_height() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_ul[1] - _text_lr[1];
   return _text_ul[1] - _text_lr[1];
 }
 }
@@ -1088,6 +1104,7 @@ get_height() const {
  */
  */
 INLINE PN_stdfloat TextNode::
 INLINE PN_stdfloat TextNode::
 get_width() const {
 get_width() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _text_lr[0] - _text_ul[0];
   return _text_lr[0] - _text_ul[0];
 }
 }
@@ -1098,6 +1115,7 @@ get_width() const {
  */
  */
 INLINE LPoint3 TextNode::
 INLINE LPoint3 TextNode::
 get_upper_left_3d() const {
 get_upper_left_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _ul3d;
   return _ul3d;
 }
 }
@@ -1108,6 +1126,7 @@ get_upper_left_3d() const {
  */
  */
 INLINE LPoint3 TextNode::
 INLINE LPoint3 TextNode::
 get_lower_right_3d() const {
 get_lower_right_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _lr3d;
   return _lr3d;
 }
 }
@@ -1118,10 +1137,22 @@ get_lower_right_3d() const {
  */
  */
 INLINE int TextNode::
 INLINE int TextNode::
 get_num_rows() const {
 get_num_rows() const {
+  MutexHolder holder(_lock);
   check_measure();
   check_measure();
   return _num_rows;
   return _num_rows;
 }
 }
 
 
+/**
+ * Generates the text, according to the parameters indicated within the
+ * TextNode, and returns a Node that may be parented within the tree to
+ * represent it.
+ */
+PT(PandaNode) TextNode::
+generate() {
+  MutexHolder holder(_lock);
+  return do_generate();
+}
+
 /**
 /**
  * Can be called after the TextNode has been fully configured, to force the
  * Can be called after the TextNode has been fully configured, to force the
  * node to recompute its text immediately, rather than waiting for it to be
  * node to recompute its text immediately, rather than waiting for it to be
@@ -1129,6 +1160,7 @@ get_num_rows() const {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 update() {
 update() {
+  MutexHolder holder(_lock);
   check_rebuild();
   check_rebuild();
 }
 }
 
 
@@ -1140,8 +1172,9 @@ update() {
  */
  */
 INLINE void TextNode::
 INLINE void TextNode::
 force_update() {
 force_update() {
-  invalidate_with_measure();
-  check_rebuild();
+  MutexHolder holder(_lock);
+  mark_internal_bounds_stale();
+  do_rebuild();
 }
 }
 
 
 /**
 /**

+ 335 - 284
panda/src/text/textNode.cxx

@@ -74,7 +74,7 @@ TextNode(const string &name) : PandaNode(name) {
   }
   }
 
 
   if (text_small_caps) {
   if (text_small_caps) {
-    set_small_caps(true);
+    TextProperties::set_small_caps(true);
   }
   }
 
 
   _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
   _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
@@ -255,11 +255,16 @@ is_whitespace(wchar_t character) const {
  */
  */
 PN_stdfloat TextNode::
 PN_stdfloat TextNode::
 calc_width(const std::wstring &line) const {
 calc_width(const std::wstring &line) const {
+  TextFont *font = get_font();
+  if (font == nullptr) {
+    return 0.0f;
+  }
+
   PN_stdfloat width = 0.0f;
   PN_stdfloat width = 0.0f;
 
 
   std::wstring::const_iterator si;
   std::wstring::const_iterator si;
   for (si = line.begin(); si != line.end(); ++si) {
   for (si = line.begin(); si != line.end(); ++si) {
-    width += calc_width(*si);
+    width += TextAssembler::calc_width(*si, *this);
   }
   }
 
 
   return width;
   return width;
@@ -272,10 +277,10 @@ void TextNode::
 output(std::ostream &out) const {
 output(std::ostream &out) const {
   PandaNode::output(out);
   PandaNode::output(out);
 
 
-  check_rebuild();
+  PT(PandaNode) internal_geom = do_get_internal_geom();
   int geom_count = 0;
   int geom_count = 0;
-  if (_internal_geom != nullptr) {
-    geom_count = count_geoms(_internal_geom);
+  if (internal_geom != nullptr) {
+    geom_count = count_geoms(internal_geom);
   }
   }
 
 
   out << " (" << geom_count << " geoms)";
   out << " (" << geom_count << " geoms)";
@@ -286,6 +291,7 @@ output(std::ostream &out) const {
  */
  */
 void TextNode::
 void TextNode::
 write(std::ostream &out, int indent_level) const {
 write(std::ostream &out, int indent_level) const {
+  MutexHolder holder(_lock);
   PandaNode::write(out, indent_level);
   PandaNode::write(out, indent_level);
   TextProperties::write(out, indent_level + 2);
   TextProperties::write(out, indent_level + 2);
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
@@ -296,167 +302,6 @@ write(std::ostream &out, int indent_level) const {
     << "text is " << get_text() << "\n";
     << "text is " << get_text() << "\n";
 }
 }
 
 
-/**
- * Generates the text, according to the parameters indicated within the
- * TextNode, and returns a Node that may be parented within the tree to
- * represent it.
- */
-PT(PandaNode) TextNode::
-generate() {
-  PStatTimer timer(_text_generate_pcollector);
-  if (text_cat.is_debug()) {
-    text_cat.debug()
-      << "Rebuilding " << get_type() << " " << get_name()
-      << " with '" << get_text() << "'\n";
-  }
-
-  // The strategy here will be to assemble together a bunch of letters,
-  // instanced from the letter hierarchy of font_def, into our own little
-  // hierarchy.
-
-  // There will be one root over the whole text block, that contains the
-  // transform passed in.  Under this root there will be another node for each
-  // row, that moves the row into the right place horizontally and vertically,
-  // and for each row, there is another node for each character.
-
-  _ul3d.set(0.0f, 0.0f, 0.0f);
-  _lr3d.set(0.0f, 0.0f, 0.0f);
-
-  // Now build a new sub-tree for all the text components.
-  string name = get_text();
-  size_t newline = name.find('\n');
-  if (newline != string::npos) {
-    name = name.substr(0, newline);
-  }
-  PT(PandaNode) root = new PandaNode(name);
-
-  if (!has_text()) {
-    return root;
-  }
-
-  TextFont *font = get_font();
-  if (font == nullptr) {
-    return root;
-  }
-
-  // Compute the overall text transform matrix.  We build the text in a Z-up
-  // coordinate system and then convert it to whatever the user asked for.
-  LMatrix4 mat =
-    LMatrix4::convert_mat(CS_zup_right, _coordinate_system) *
-    _transform;
-
-  CPT(TransformState) transform = TransformState::make_mat(mat);
-  root->set_transform(transform);
-
-  std::wstring wtext = get_wtext();
-
-  // Assemble the text.
-  TextAssembler assembler(this);
-  assembler.set_properties(*this);
-  assembler.set_max_rows(_max_rows);
-  assembler.set_usage_hint(_usage_hint);
-  assembler.set_dynamic_merge((_flatten_flags & FF_dynamic_merge) != 0);
-  bool all_set = assembler.set_wtext(wtext);
-  if (all_set) {
-    // No overflow.
-    _flags &= ~F_has_overflow;
-  } else {
-    // Overflow.
-    _flags |= F_has_overflow;
-  }
-
-  PT(PandaNode) text_root = assembler.assemble_text();
-  _text_ul = assembler.get_ul();
-  _text_lr = assembler.get_lr();
-  _num_rows = assembler.get_num_rows();
-  _wordwrapped_wtext = assembler.get_wordwrapped_wtext();
-
-  // Parent the text in.
-  PT(PandaNode) text = new PandaNode("text");
-  root->add_child(text, get_draw_order() + 2);
-  text->add_child(text_root);
-
-  // Save the bounding-box information about the text in a form friendly to
-  // the user.
-  const LVector2 &ul = assembler.get_ul();
-  const LVector2 &lr = assembler.get_lr();
-  _ul3d.set(ul[0], 0.0f, ul[1]);
-  _lr3d.set(lr[0], 0.0f, lr[1]);
-
-  _ul3d = _ul3d * _transform;
-  _lr3d = _lr3d * _transform;
-
-  // Incidentally, that means we don't need to measure the text now.
-  _flags &= ~F_needs_measure;
-
-  // Now flatten our hierarchy to get rid of the transforms we put in,
-  // applying them to the vertices.
-
-  NodePath root_np(root);
-  if (_flatten_flags & FF_strong) {
-    root_np.flatten_strong();
-  } else if (_flatten_flags & FF_medium) {
-    root_np.flatten_medium();
-  } else if (_flatten_flags & FF_light) {
-    root_np.flatten_light();
-  }
-
-  // Now deal with the decorations.
-
-  if (has_card()) {
-    PT(PandaNode) card_root;
-    if (has_card_border()) {
-      card_root = make_card_with_border();
-    } else {
-      card_root = make_card();
-    }
-    card_root->set_transform(transform);
-    card_root->set_attrib(ColorAttrib::make_flat(get_card_color()));
-    if (get_card_color()[3] != 1.0f) {
-      card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
-    }
-    if (has_card_texture()) {
-      card_root->set_attrib(TextureAttrib::make(get_card_texture()));
-    }
-
-    if (has_bin()) {
-      card_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order()));
-    }
-
-    // We always apply attribs down to the card vertices.
-    SceneGraphReducer gr;
-    gr.apply_attribs(card_root);
-
-    // In order to decal the text onto the card, the card must become the
-    // parent of the text.
-    card_root->add_child(root);
-    root = card_root;
-
-    if (get_card_decal()) {
-      card_root->set_effect(DecalEffect::make());
-    }
-  }
-
-  if (has_frame()) {
-    PT(PandaNode) frame_root = make_frame();
-    frame_root->set_transform(transform);
-    root->add_child(frame_root, get_draw_order() + 1);
-    frame_root->set_attrib(ColorAttrib::make_flat(get_frame_color()));
-    if (get_frame_color()[3] != 1.0f) {
-      frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
-    }
-
-    if (has_bin()) {
-      frame_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order() + 1));
-    }
-
-    SceneGraphReducer gr;
-    gr.apply_attribs(frame_root);
-  }
-
-  return root;
-}
-
 /**
 /**
  * Returns the actual node that is used internally to render the text, if the
  * Returns the actual node that is used internally to render the text, if the
  * TextNode is parented within the scene graph.
  * TextNode is parented within the scene graph.
@@ -465,14 +310,22 @@ generate() {
  * you want to get a handle to geometry that represents the text.  This method
  * you want to get a handle to geometry that represents the text.  This method
  * is provided as a debugging aid only.
  * is provided as a debugging aid only.
  */
  */
-PandaNode *TextNode::
+PT(PandaNode) TextNode::
 get_internal_geom() const {
 get_internal_geom() const {
   // Output a nuisance warning to discourage the naive from calling this
   // Output a nuisance warning to discourage the naive from calling this
   // method accidentally.
   // method accidentally.
   text_cat.info()
   text_cat.info()
     << "TextNode::get_internal_geom() called.\n";
     << "TextNode::get_internal_geom() called.\n";
-  check_rebuild();
-  return _internal_geom;
+  return do_get_internal_geom();
+}
+
+/**
+ * Called whenever the text has been changed.
+ */
+void TextNode::
+text_changed() {
+  MutexHolder holder(_lock);
+  invalidate_with_measure();
 }
 }
 
 
 /**
 /**
@@ -503,6 +356,7 @@ get_unsafe_to_apply_attribs() const {
 void TextNode::
 void TextNode::
 apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
 apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
                           GeomTransformer &transformer) {
                           GeomTransformer &transformer) {
+  MutexHolder holder(_lock);
   if ((attrib_types & SceneGraphReducer::TT_transform) != 0) {
   if ((attrib_types & SceneGraphReducer::TT_transform) != 0) {
     const LMatrix4 &mat = attribs._transform->get_mat();
     const LMatrix4 &mat = attribs._transform->get_mat();
     _transform *= mat;
     _transform *= mat;
@@ -520,10 +374,11 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
       const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color);
       const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color);
       if (ca->get_color_type() == ColorAttrib::T_flat) {
       if (ca->get_color_type() == ColorAttrib::T_flat) {
         const LColor &c = ca->get_color();
         const LColor &c = ca->get_color();
-        set_text_color(c);
-        set_frame_color(c);
-        set_card_color(c);
-        set_shadow_color(c);
+        TextProperties::set_text_color(c);
+        TextProperties::set_shadow_color(c);
+        _frame_color = c;
+        _card_color = c;
+        invalidate_no_measure();
       }
       }
     }
     }
   }
   }
@@ -533,29 +388,17 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
       const LVecBase4 &s = csa->get_scale();
       const LVecBase4 &s = csa->get_scale();
       if (s != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)) {
       if (s != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)) {
         LVecBase4 tc = get_text_color();
         LVecBase4 tc = get_text_color();
-        tc[0] *= s[0];
-        tc[1] *= s[1];
-        tc[2] *= s[2];
-        tc[3] *= s[3];
-        set_text_color(tc);
+        tc.componentwise_mult(s);
+        TextProperties::set_text_color(tc);
+
         LVecBase4 sc = get_shadow_color();
         LVecBase4 sc = get_shadow_color();
-        sc[0] *= s[0];
-        sc[1] *= s[1];
-        sc[2] *= s[2];
-        sc[3] *= s[3];
-        set_shadow_color(sc);
-        LVecBase4 fc = get_frame_color();
-        fc[0] *= s[0];
-        fc[1] *= s[1];
-        fc[2] *= s[2];
-        fc[3] *= s[3];
-        set_frame_color(fc);
-        LVecBase4 cc = get_card_color();
-        cc[0] *= s[0];
-        cc[1] *= s[1];
-        cc[2] *= s[2];
-        cc[3] *= s[3];
-        set_card_color(cc);
+        sc.componentwise_mult(s);
+        TextProperties::set_shadow_color(sc);
+
+        _frame_color.componentwise_mult(s);
+        _card_color.componentwise_mult(s);
+
+        invalidate_no_measure();
       }
       }
     }
     }
   }
   }
@@ -587,11 +430,10 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
     PandaNode::calc_tight_bounds(min_point, max_point, found_any, transform,
     PandaNode::calc_tight_bounds(min_point, max_point, found_any, transform,
                                  current_thread);
                                  current_thread);
 
 
-  check_rebuild();
-
-  if (_internal_geom != nullptr) {
-    _internal_geom->calc_tight_bounds(min_point, max_point,
-                                      found_any, next_transform, current_thread);
+  PT(PandaNode) geom = do_get_internal_geom();
+  if (geom != nullptr) {
+    geom->calc_tight_bounds(min_point, max_point,
+                            found_any, next_transform, current_thread);
   }
   }
 
 
   return next_transform;
   return next_transform;
@@ -617,10 +459,11 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
  */
  */
 bool TextNode::
 bool TextNode::
 cull_callback(CullTraverser *trav, CullTraverserData &data) {
 cull_callback(CullTraverser *trav, CullTraverserData &data) {
-  check_rebuild();
-  if (_internal_geom != nullptr) {
+
+  PT(PandaNode) internal_geom = do_get_internal_geom();
+  if (internal_geom != nullptr) {
     // Render the text with this node.
     // Render the text with this node.
-    CullTraverserData next_data(data, _internal_geom);
+    CullTraverserData next_data(data, internal_geom);
     trav->traverse(next_data);
     trav->traverse(next_data);
   }
   }
 
 
@@ -656,17 +499,20 @@ compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
 
 
   // Now enclose the bounding box around the text.  We can do this without
   // Now enclose the bounding box around the text.  We can do this without
   // actually generating the text, if we have at least measured it.
   // actually generating the text, if we have at least measured it.
-  check_measure();
-
   LPoint3 vertices[8];
   LPoint3 vertices[8];
-  vertices[0].set(_ul3d[0], _ul3d[1], _ul3d[2]);
-  vertices[1].set(_ul3d[0], _ul3d[1], _lr3d[2]);
-  vertices[2].set(_ul3d[0], _lr3d[1], _ul3d[2]);
-  vertices[3].set(_ul3d[0], _lr3d[1], _lr3d[2]);
-  vertices[4].set(_lr3d[0], _ul3d[1], _ul3d[2]);
-  vertices[5].set(_lr3d[0], _ul3d[1], _lr3d[2]);
-  vertices[6].set(_lr3d[0], _lr3d[1], _ul3d[2]);
-  vertices[7].set(_lr3d[0], _lr3d[1], _lr3d[2]);
+  {
+    MutexHolder holder(_lock);
+    check_measure();
+
+    vertices[0].set(_ul3d[0], _ul3d[1], _ul3d[2]);
+    vertices[1].set(_ul3d[0], _ul3d[1], _lr3d[2]);
+    vertices[2].set(_ul3d[0], _lr3d[1], _ul3d[2]);
+    vertices[3].set(_ul3d[0], _lr3d[1], _lr3d[2]);
+    vertices[4].set(_lr3d[0], _ul3d[1], _ul3d[2]);
+    vertices[5].set(_lr3d[0], _ul3d[1], _lr3d[2]);
+    vertices[6].set(_lr3d[0], _lr3d[1], _ul3d[2]);
+    vertices[7].set(_lr3d[0], _lr3d[1], _lr3d[2]);
+  }
 
 
   gbv->around(vertices, vertices + 8);
   gbv->around(vertices, vertices + 8);
 
 
@@ -681,9 +527,8 @@ compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
 void TextNode::
 void TextNode::
 r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
 r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
                 GeomTransformer &transformer, Thread *current_thread) {
                 GeomTransformer &transformer, Thread *current_thread) {
-  check_rebuild();
 
 
-  PandaNode *child = _internal_geom;
+  PT(PandaNode) child = do_get_internal_geom();
   if (child != nullptr) {
   if (child != nullptr) {
     CPT(RenderState) child_state = node_state->compose(child->get_state());
     CPT(RenderState) child_state = node_state->compose(child->get_state());
     child->r_prepare_scene(gsg, child_state, transformer, current_thread);
     child->r_prepare_scene(gsg, child_state, transformer, current_thread);
@@ -698,8 +543,9 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
  */
  */
 void TextNode::
 void TextNode::
 do_rebuild() {
 do_rebuild() {
+  nassertv(_lock.debug_is_locked());
   _flags &= ~(F_needs_rebuild | F_needs_measure);
   _flags &= ~(F_needs_rebuild | F_needs_measure);
-  _internal_geom = generate();
+  _internal_geom = do_generate();
 }
 }
 
 
 
 
@@ -713,32 +559,216 @@ do_measure() {
   do_rebuild();
   do_rebuild();
 }
 }
 
 
+/**
+ * Generates the text, according to the parameters indicated within the
+ * TextNode, and returns a Node that may be parented within the tree to
+ * represent it.
+ */
+PT(PandaNode) TextNode::
+do_generate() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+
+  PStatTimer timer(_text_generate_pcollector);
+  if (text_cat.is_debug()) {
+    text_cat.debug()
+      << "Rebuilding " << get_type() << " " << get_name()
+      << " with '" << get_text() << "'\n";
+  }
+
+  // The strategy here will be to assemble together a bunch of letters,
+  // instanced from the letter hierarchy of font_def, into our own little
+  // hierarchy.
+
+  // There will be one root over the whole text block, that contains the
+  // transform passed in.  Under this root there will be another node for each
+  // row, that moves the row into the right place horizontally and vertically,
+  // and for each row, there is another node for each character.
+
+  _ul3d.set(0.0f, 0.0f, 0.0f);
+  _lr3d.set(0.0f, 0.0f, 0.0f);
+
+  // Now build a new sub-tree for all the text components.
+  string name = get_text();
+  size_t newline = name.find('\n');
+  if (newline != string::npos) {
+    name = name.substr(0, newline);
+  }
+  PT(PandaNode) root = new PandaNode(name);
+
+  if (!has_text()) {
+    return root;
+  }
+
+  TextFont *font = get_font();
+  if (font == nullptr) {
+    return root;
+  }
+
+  // Compute the overall text transform matrix.  We build the text in a Z-up
+  // coordinate system and then convert it to whatever the user asked for.
+  LMatrix4 mat =
+    LMatrix4::convert_mat(CS_zup_right, _coordinate_system) *
+    _transform;
+
+  CPT(TransformState) transform = TransformState::make_mat(mat);
+  root->set_transform(transform);
+
+  std::wstring wtext = get_wtext();
+
+  // Assemble the text.
+  TextAssembler assembler(this);
+  assembler.set_properties(*this);
+  assembler.set_max_rows(_max_rows);
+  assembler.set_usage_hint(_usage_hint);
+  assembler.set_dynamic_merge((_flatten_flags & FF_dynamic_merge) != 0);
+  bool all_set = assembler.set_wtext(wtext);
+  if (all_set) {
+    // No overflow.
+    _flags &= ~F_has_overflow;
+  } else {
+    // Overflow.
+    _flags |= F_has_overflow;
+  }
+
+  PT(PandaNode) text_root = assembler.assemble_text();
+  _text_ul = assembler.get_ul();
+  _text_lr = assembler.get_lr();
+  _num_rows = assembler.get_num_rows();
+  _wordwrapped_wtext = assembler.get_wordwrapped_wtext();
+
+  // Parent the text in.
+  PT(PandaNode) text = new PandaNode("text");
+  root->add_child(text, get_draw_order() + 2);
+  text->add_child(text_root);
+
+  // Save the bounding-box information about the text in a form friendly to
+  // the user.
+  const LVector2 &ul = assembler.get_ul();
+  const LVector2 &lr = assembler.get_lr();
+  _ul3d.set(ul[0], 0.0f, ul[1]);
+  _lr3d.set(lr[0], 0.0f, lr[1]);
+
+  _ul3d = _ul3d * _transform;
+  _lr3d = _lr3d * _transform;
+
+  // Incidentally, that means we don't need to measure the text now.
+  _flags &= ~F_needs_measure;
+
+  // Now flatten our hierarchy to get rid of the transforms we put in,
+  // applying them to the vertices.
+
+  NodePath root_np(root);
+  if (_flatten_flags & FF_strong) {
+    root_np.flatten_strong();
+  } else if (_flatten_flags & FF_medium) {
+    root_np.flatten_medium();
+  } else if (_flatten_flags & FF_light) {
+    root_np.flatten_light();
+  }
+
+  // Now deal with the decorations.
+
+  if (_flags & F_has_card) {
+    PT(PandaNode) card_root;
+    if (_flags & F_has_card_border) {
+      card_root = make_card_with_border();
+    } else {
+      card_root = make_card();
+    }
+    card_root->set_transform(transform);
+    card_root->set_attrib(ColorAttrib::make_flat(_card_color));
+    if (_card_color[3] != 1.0f) {
+      card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+    if (_flags & F_has_card_texture) {
+      card_root->set_attrib(TextureAttrib::make(_card_texture));
+    }
+
+    if (has_bin()) {
+      card_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order()));
+    }
+
+    // We always apply attribs down to the card vertices.
+    SceneGraphReducer gr;
+    gr.apply_attribs(card_root);
+
+    // In order to decal the text onto the card, the card must become the
+    // parent of the text.
+    card_root->add_child(root);
+    root = card_root;
+
+    if (_flags & F_card_decal) {
+      card_root->set_effect(DecalEffect::make());
+    }
+  }
+
+  if (_flags & F_has_frame) {
+    PT(PandaNode) frame_root = make_frame();
+    frame_root->set_transform(transform);
+    root->add_child(frame_root, get_draw_order() + 1);
+    frame_root->set_attrib(ColorAttrib::make_flat(_frame_color));
+    if (_frame_color[3] != 1.0f) {
+      frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+
+    if (has_bin()) {
+      frame_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order() + 1));
+    }
+
+    SceneGraphReducer gr;
+    gr.apply_attribs(frame_root);
+  }
+
+  return root;
+}
+
+/**
+ * Returns the actual node that is used internally to render the text, if the
+ * TextNode is parented within the scene graph.
+ */
+PT(PandaNode) TextNode::
+do_get_internal_geom() const {
+  MutexHolder holder(_lock);
+  check_rebuild();
+  return _internal_geom;
+}
+
 /**
 /**
  * Creates a frame around the text.
  * Creates a frame around the text.
  */
  */
 PT(PandaNode) TextNode::
 PT(PandaNode) TextNode::
 make_frame() {
 make_frame() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   PT(GeomNode) frame_node = new GeomNode("frame");
   PT(GeomNode) frame_node = new GeomNode("frame");
 
 
-  LVector4 dimensions = get_frame_actual();
-  PN_stdfloat left = dimensions[0];
-  PN_stdfloat right = dimensions[1];
-  PN_stdfloat bottom = dimensions[2];
-  PN_stdfloat top = dimensions[3];
+  PN_stdfloat left = _frame_ul[0];
+  PN_stdfloat right = _frame_lr[0];
+  PN_stdfloat bottom = _frame_lr[1];
+  PN_stdfloat top = _frame_ul[1];
+
+  if (_flags & F_frame_as_margin) {
+    left = _text_ul[0] - left;
+    right = _text_lr[0] + right;
+    bottom = _text_lr[1] - bottom;
+    top = _text_ul[1] + top;
+  }
 
 
   CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _frame_width);
   CPT(RenderAttrib) thick = RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _frame_width);
   CPT(RenderState) state = RenderState::make(thick);
   CPT(RenderState) state = RenderState::make(thick);
 
 
   PT(GeomVertexData) vdata = new GeomVertexData
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("text", GeomVertexFormat::get_v3(), get_usage_hint());
+    ("text", GeomVertexFormat::get_v3(), _usage_hint);
+  vdata->unclean_set_num_rows(4);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
 
 
-  vertex.add_data3(left, 0.0f, top);
-  vertex.add_data3(left, 0.0f, bottom);
-  vertex.add_data3(right, 0.0f, bottom);
-  vertex.add_data3(right, 0.0f, top);
+  vertex.set_data3(left, 0.0f, top);
+  vertex.set_data3(left, 0.0f, bottom);
+  vertex.set_data3(right, 0.0f, bottom);
+  vertex.set_data3(right, 0.0f, top);
 
 
-  PT(GeomLinestrips) frame = new GeomLinestrips(get_usage_hint());
+  PT(GeomLinestrips) frame = new GeomLinestrips(_usage_hint);
   frame->add_consecutive_vertices(0, 4);
   frame->add_consecutive_vertices(0, 4);
   frame->add_vertex(0);
   frame->add_vertex(0);
   frame->close_primitive();
   frame->close_primitive();
@@ -747,8 +777,8 @@ make_frame() {
   geom->add_primitive(frame);
   geom->add_primitive(frame);
   frame_node->add_geom(geom, state);
   frame_node->add_geom(geom, state);
 
 
-  if (get_frame_corners()) {
-    PT(GeomPoints) corners = new GeomPoints(get_usage_hint());
+  if (_flags & F_frame_corners) {
+    PT(GeomPoints) corners = new GeomPoints(_usage_hint);
     corners->add_consecutive_vertices(0, 4);
     corners->add_consecutive_vertices(0, 4);
     PT(Geom) geom2 = new Geom(vdata);
     PT(Geom) geom2 = new Geom(vdata);
     geom2->add_primitive(corners);
     geom2->add_primitive(corners);
@@ -763,30 +793,40 @@ make_frame() {
  */
  */
 PT(PandaNode) TextNode::
 PT(PandaNode) TextNode::
 make_card() {
 make_card() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   PT(GeomNode) card_node = new GeomNode("card");
   PT(GeomNode) card_node = new GeomNode("card");
 
 
-  LVector4 dimensions = get_card_actual();
-  PN_stdfloat left = dimensions[0];
-  PN_stdfloat right = dimensions[1];
-  PN_stdfloat bottom = dimensions[2];
-  PN_stdfloat top = dimensions[3];
+  PN_stdfloat left = _card_ul[0];
+  PN_stdfloat right = _card_lr[0];
+  PN_stdfloat bottom = _card_lr[1];
+  PN_stdfloat top = _card_ul[1];
+
+  if (_flags & F_card_as_margin) {
+    left = _text_ul[0] - left;
+    right = _text_lr[0] + right;
+    bottom = _text_lr[1] - bottom;
+    top = _text_ul[1] + top;
+  }
 
 
   PT(GeomVertexData) vdata = new GeomVertexData
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("text", GeomVertexFormat::get_v3t2(), get_usage_hint());
+    ("text", GeomVertexFormat::get_v3t2(), _usage_hint);
+  vdata->unclean_set_num_rows(4);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
 
 
-  vertex.add_data3(left, 0.0f, top);
-  vertex.add_data3(left, 0.0f, bottom);
-  vertex.add_data3(right, 0.0f, top);
-  vertex.add_data3(right, 0.0f, bottom);
+  vertex.set_data3(left, 0.0f, top);
+  vertex.set_data3(left, 0.0f, bottom);
+  vertex.set_data3(right, 0.0f, top);
+  vertex.set_data3(right, 0.0f, bottom);
 
 
-  texcoord.add_data2(0.0f, 1.0f);
-  texcoord.add_data2(0.0f, 0.0f);
-  texcoord.add_data2(1.0f, 1.0f);
-  texcoord.add_data2(1.0f, 0.0f);
+  texcoord.set_data2(0.0f, 1.0f);
+  texcoord.set_data2(0.0f, 0.0f);
+  texcoord.set_data2(1.0f, 1.0f);
+  texcoord.set_data2(1.0f, 0.0f);
 
 
-  PT(GeomTristrips) card = new GeomTristrips(get_usage_hint());
+  PT(GeomTristrips) card = new GeomTristrips(_usage_hint);
   card->add_consecutive_vertices(0, 4);
   card->add_consecutive_vertices(0, 4);
   card->close_primitive();
   card->close_primitive();
 
 
@@ -805,13 +845,22 @@ make_card() {
  */
  */
 PT(PandaNode) TextNode::
 PT(PandaNode) TextNode::
 make_card_with_border() {
 make_card_with_border() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   PT(GeomNode) card_node = new GeomNode("card");
   PT(GeomNode) card_node = new GeomNode("card");
 
 
-  LVector4 dimensions = get_card_actual();
-  PN_stdfloat left = dimensions[0];
-  PN_stdfloat right = dimensions[1];
-  PN_stdfloat bottom = dimensions[2];
-  PN_stdfloat top = dimensions[3];
+  PN_stdfloat left = _card_ul[0];
+  PN_stdfloat right = _card_lr[0];
+  PN_stdfloat bottom = _card_lr[1];
+  PN_stdfloat top = _card_ul[1];
+
+  if (_flags & F_card_as_margin) {
+    left = _text_ul[0] - left;
+    right = _text_lr[0] + right;
+    bottom = _text_lr[1] - bottom;
+    top = _text_ul[1] + top;
+  }
 
 
 /*
 /*
  * we now create three tri-strips instead of one with vertices arranged as
  * we now create three tri-strips instead of one with vertices arranged as
@@ -820,57 +869,59 @@ make_card_with_border() {
  */
  */
 
 
   PT(GeomVertexData) vdata = new GeomVertexData
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("text", GeomVertexFormat::get_v3t2(), get_usage_hint());
+    ("text", GeomVertexFormat::get_v3t2(), _usage_hint);
+  vdata->unclean_set_num_rows(16);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
   GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
 
 
   // verts 1,2,3,4
   // verts 1,2,3,4
-  vertex.add_data3(left, 0.02, top);
-  vertex.add_data3(left, 0.02, top - _card_border_size);
-  vertex.add_data3(left + _card_border_size, 0.02, top);
-  vertex.add_data3(left + _card_border_size, 0.02,
+  vertex.set_data3(left, 0.02, top);
+  vertex.set_data3(left, 0.02, top - _card_border_size);
+  vertex.set_data3(left + _card_border_size, 0.02, top);
+  vertex.set_data3(left + _card_border_size, 0.02,
                     top - _card_border_size);
                     top - _card_border_size);
   // verts 5,6,7,8
   // verts 5,6,7,8
-  vertex.add_data3(right - _card_border_size, 0.02, top);
-  vertex.add_data3(right - _card_border_size, 0.02,
+  vertex.set_data3(right - _card_border_size, 0.02, top);
+  vertex.set_data3(right - _card_border_size, 0.02,
                     top - _card_border_size);
                     top - _card_border_size);
-  vertex.add_data3(right, 0.02, top);
-  vertex.add_data3(right, 0.02, top - _card_border_size);
+  vertex.set_data3(right, 0.02, top);
+  vertex.set_data3(right, 0.02, top - _card_border_size);
   // verts 9,10,11,12
   // verts 9,10,11,12
-  vertex.add_data3(left, 0.02, bottom + _card_border_size);
-  vertex.add_data3(left, 0.02, bottom);
-  vertex.add_data3(left + _card_border_size, 0.02,
+  vertex.set_data3(left, 0.02, bottom + _card_border_size);
+  vertex.set_data3(left, 0.02, bottom);
+  vertex.set_data3(left + _card_border_size, 0.02,
                     bottom + _card_border_size);
                     bottom + _card_border_size);
-  vertex.add_data3(left + _card_border_size, 0.02, bottom);
+  vertex.set_data3(left + _card_border_size, 0.02, bottom);
   // verts 13,14,15,16
   // verts 13,14,15,16
-  vertex.add_data3(right - _card_border_size, 0.02,
+  vertex.set_data3(right - _card_border_size, 0.02,
                     bottom + _card_border_size);
                     bottom + _card_border_size);
-  vertex.add_data3(right - _card_border_size, 0.02, bottom);
-  vertex.add_data3(right, 0.02, bottom + _card_border_size);
-  vertex.add_data3(right, 0.02, bottom);
-
-  texcoord.add_data2(0.0f, 1.0f); //1
-  texcoord.add_data2(0.0f, 1.0f - _card_border_uv_portion); //2
-  texcoord.add_data2(0.0f + _card_border_uv_portion, 1.0f); //3
-  texcoord.add_data2(0.0f + _card_border_uv_portion,
+  vertex.set_data3(right - _card_border_size, 0.02, bottom);
+  vertex.set_data3(right, 0.02, bottom + _card_border_size);
+  vertex.set_data3(right, 0.02, bottom);
+
+  texcoord.set_data2(0.0f, 1.0f); //1
+  texcoord.set_data2(0.0f, 1.0f - _card_border_uv_portion); //2
+  texcoord.set_data2(0.0f + _card_border_uv_portion, 1.0f); //3
+  texcoord.set_data2(0.0f + _card_border_uv_portion,
                       1.0f - _card_border_uv_portion); //4
                       1.0f - _card_border_uv_portion); //4
-  texcoord.add_data2(1.0f -_card_border_uv_portion, 1.0f); //5
-  texcoord.add_data2(1.0f -_card_border_uv_portion,
+  texcoord.set_data2(1.0f -_card_border_uv_portion, 1.0f); //5
+  texcoord.set_data2(1.0f -_card_border_uv_portion,
                       1.0f - _card_border_uv_portion); //6
                       1.0f - _card_border_uv_portion); //6
-  texcoord.add_data2(1.0f, 1.0f); //7
-  texcoord.add_data2(1.0f, 1.0f - _card_border_uv_portion); //8
+  texcoord.set_data2(1.0f, 1.0f); //7
+  texcoord.set_data2(1.0f, 1.0f - _card_border_uv_portion); //8
 
 
-  texcoord.add_data2(0.0f, _card_border_uv_portion); //9
-  texcoord.add_data2(0.0f, 0.0f); //10
-  texcoord.add_data2(_card_border_uv_portion, _card_border_uv_portion); //11
-  texcoord.add_data2(_card_border_uv_portion, 0.0f); //12
+  texcoord.set_data2(0.0f, _card_border_uv_portion); //9
+  texcoord.set_data2(0.0f, 0.0f); //10
+  texcoord.set_data2(_card_border_uv_portion, _card_border_uv_portion); //11
+  texcoord.set_data2(_card_border_uv_portion, 0.0f); //12
 
 
-  texcoord.add_data2(1.0f - _card_border_uv_portion, _card_border_uv_portion);//13
-  texcoord.add_data2(1.0f - _card_border_uv_portion, 0.0f);//14
-  texcoord.add_data2(1.0f, _card_border_uv_portion);//15
-  texcoord.add_data2(1.0f, 0.0f);//16
+  texcoord.set_data2(1.0f - _card_border_uv_portion, _card_border_uv_portion);//13
+  texcoord.set_data2(1.0f - _card_border_uv_portion, 0.0f);//14
+  texcoord.set_data2(1.0f, _card_border_uv_portion);//15
+  texcoord.set_data2(1.0f, 0.0f);//16
 
 
-  PT(GeomTristrips) card = new GeomTristrips(get_usage_hint());
+  PT(GeomTristrips) card = new GeomTristrips(_usage_hint);
+  card->reserve_num_vertices(24);
 
 
   // tristrip #1
   // tristrip #1
   card->add_consecutive_vertices(0, 8);
   card->add_consecutive_vertices(0, 8);

+ 11 - 16
panda/src/text/textNode.h

@@ -24,6 +24,8 @@
 #include "pandaNode.h"
 #include "pandaNode.h"
 #include "luse.h"
 #include "luse.h"
 #include "geom.h"
 #include "geom.h"
+#include "pmutex.h"
+#include "mutexHolder.h"
 
 
 /**
 /**
  * The primary interface to this module.  This class does basic text assembly;
  * The primary interface to this module.  This class does basic text assembly;
@@ -180,14 +182,6 @@ PUBLISHED:
   INLINE void set_glyph_shift(PN_stdfloat glyph_shift);
   INLINE void set_glyph_shift(PN_stdfloat glyph_shift);
   INLINE void clear_glyph_shift();
   INLINE void clear_glyph_shift();
 
 
-  // These methods are inherited from TextEncoder, but we override here so we
-  // can flag the TextNode as dirty when they have been changed.
-  INLINE void set_text(const std::string &text);
-  INLINE void set_text(const std::string &text, Encoding encoding);
-  INLINE void clear_text();
-  INLINE void append_text(const std::string &text);
-  INLINE void append_unicode_char(wchar_t character);
-
   // After the text has been set, you can query this to determine how it will
   // After the text has been set, you can query this to determine how it will
   // be wordwrapped.
   // be wordwrapped.
   INLINE std::string get_wordwrapped_text() const;
   INLINE std::string get_wordwrapped_text() const;
@@ -201,10 +195,6 @@ PUBLISHED:
   bool has_character(wchar_t character) const;
   bool has_character(wchar_t character) const;
   bool is_whitespace(wchar_t character) const;
   bool is_whitespace(wchar_t character) const;
 
 
-  // Direct support for wide-character strings.
-  INLINE void set_wtext(const std::wstring &wtext);
-  INLINE void append_wtext(const std::wstring &text);
-
   INLINE std::wstring get_wordwrapped_wtext() const;
   INLINE std::wstring get_wordwrapped_wtext() const;
   PN_stdfloat calc_width(const std::wstring &line) const;
   PN_stdfloat calc_width(const std::wstring &line) const;
 
 
@@ -225,11 +215,11 @@ PUBLISHED:
 
 
   INLINE int get_num_rows() const;
   INLINE int get_num_rows() const;
 
 
-  PT(PandaNode) generate();
+  INLINE PT(PandaNode) generate();
   INLINE void update();
   INLINE void update();
   INLINE void force_update();
   INLINE void force_update();
 
 
-  PandaNode *get_internal_geom() const;
+  PT(PandaNode) get_internal_geom() const;
 
 
 PUBLISHED:
 PUBLISHED:
   MAKE_PROPERTY(max_rows, get_max_rows, set_max_rows);
   MAKE_PROPERTY(max_rows, get_max_rows, set_max_rows);
@@ -243,8 +233,6 @@ PUBLISHED:
   MAKE_PROPERTY(usage_hint, get_usage_hint, set_usage_hint);
   MAKE_PROPERTY(usage_hint, get_usage_hint, set_usage_hint);
   MAKE_PROPERTY(flatten_flags, get_flatten_flags, set_flatten_flags);
   MAKE_PROPERTY(flatten_flags, get_flatten_flags, set_flatten_flags);
 
 
-  MAKE_PROPERTY(text, get_text, set_text);
-
   MAKE_PROPERTY2(font, has_font, get_font, set_font, clear_font);
   MAKE_PROPERTY2(font, has_font, get_font, set_font, clear_font);
   MAKE_PROPERTY2(small_caps, has_small_caps, get_small_caps,
   MAKE_PROPERTY2(small_caps, has_small_caps, get_small_caps,
                              set_small_caps, clear_small_caps);
                              set_small_caps, clear_small_caps);
@@ -279,6 +267,9 @@ PUBLISHED:
                              set_text_scale, clear_text_scale);
                              set_text_scale, clear_text_scale);
 
 
 public:
 public:
+  // From parent class TextEncoder;
+  virtual void text_changed() final;
+
   // From parent class PandaNode
   // From parent class PandaNode
   virtual int get_unsafe_to_apply_attribs() const;
   virtual int get_unsafe_to_apply_attribs() const;
   virtual void apply_attribs_to_vertices(const AccumulatedAttribs &attribs,
   virtual void apply_attribs_to_vertices(const AccumulatedAttribs &attribs,
@@ -312,12 +303,16 @@ private:
   void do_rebuild();
   void do_rebuild();
   void do_measure();
   void do_measure();
 
 
+  PT(PandaNode) do_generate();
+  PT(PandaNode) do_get_internal_geom() const;
+
   PT(PandaNode) make_frame();
   PT(PandaNode) make_frame();
   PT(PandaNode) make_card();
   PT(PandaNode) make_card();
   PT(PandaNode) make_card_with_border();
   PT(PandaNode) make_card_with_border();
 
 
   static int count_geoms(PandaNode *node);
   static int count_geoms(PandaNode *node);
 
 
+  Mutex _lock;
   PT(PandaNode) _internal_geom;
   PT(PandaNode) _internal_geom;
 
 
   PT(Texture) _card_texture;
   PT(Texture) _card_texture;

+ 35 - 11
panda/src/windisplay/winGraphicsWindow.cxx

@@ -283,6 +283,25 @@ set_properties_now(WindowProperties &properties) {
     return;
     return;
   }
   }
 
 
+  if (properties.has_undecorated() ||
+      properties.has_fixed_size()) {
+    if (properties.has_undecorated()) {
+      _properties.set_undecorated(properties.get_undecorated());
+      properties.clear_undecorated();
+    }
+    if (properties.has_fixed_size()) {
+      _properties.set_fixed_size(properties.get_fixed_size());
+      properties.clear_fixed_size();
+    }
+    DWORD window_style = make_style(_properties);
+    SetWindowLong(_hWnd, GWL_STYLE, window_style);
+
+    // We need to call this to ensure that the style change takes effect.
+    SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,
+      SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE |
+      SWP_FRAMECHANGED | SWP_NOSENDCHANGING | SWP_SHOWWINDOW);
+  }
+
   if (properties.has_title()) {
   if (properties.has_title()) {
     std::string title = properties.get_title();
     std::string title = properties.get_title();
     _properties.set_title(title);
     _properties.set_title(title);
@@ -487,7 +506,7 @@ open_window() {
   // CreateWindow() and know which window it is sending events to even before
   // CreateWindow() and know which window it is sending events to even before
   // it gives us a handle.  Warning: this is not thread safe!
   // it gives us a handle.  Warning: this is not thread safe!
   _creating_window = this;
   _creating_window = this;
-  bool opened = open_graphic_window(is_fullscreen());
+  bool opened = open_graphic_window();
   _creating_window = nullptr;
   _creating_window = nullptr;
 
 
   if (!opened) {
   if (!opened) {
@@ -865,7 +884,9 @@ do_fullscreen_switch() {
     return false;
     return false;
   }
   }
 
 
-  DWORD window_style = make_style(true);
+  WindowProperties props(_properties);
+  props.set_fullscreen(true);
+  DWORD window_style = make_style(props);
   SetWindowLong(_hWnd, GWL_STYLE, window_style);
   SetWindowLong(_hWnd, GWL_STYLE, window_style);
 
 
   WINDOW_METRICS metrics;
   WINDOW_METRICS metrics;
@@ -885,7 +906,10 @@ do_fullscreen_switch() {
 bool WinGraphicsWindow::
 bool WinGraphicsWindow::
 do_windowed_switch() {
 do_windowed_switch() {
   do_fullscreen_disable();
   do_fullscreen_disable();
-  DWORD window_style = make_style(false);
+
+  WindowProperties props(_properties);
+  props.set_fullscreen(false);
+  DWORD window_style = make_style(props);
   SetWindowLong(_hWnd, GWL_STYLE, window_style);
   SetWindowLong(_hWnd, GWL_STYLE, window_style);
 
 
   WINDOW_METRICS metrics;
   WINDOW_METRICS metrics;
@@ -928,7 +952,7 @@ support_overlay_window(bool) {
  * Constructs a dwStyle for the specified mode, be it windowed or fullscreen.
  * Constructs a dwStyle for the specified mode, be it windowed or fullscreen.
  */
  */
 DWORD WinGraphicsWindow::
 DWORD WinGraphicsWindow::
-make_style(bool fullscreen) {
+make_style(const WindowProperties &properties) {
   // from MSDN: An OpenGL window has its own pixel format.  Because of this,
   // from MSDN: An OpenGL window has its own pixel format.  Because of this,
   // only device contexts retrieved for the client area of an OpenGL window
   // only device contexts retrieved for the client area of an OpenGL window
   // are allowed to draw into the window.  As a result, an OpenGL window
   // are allowed to draw into the window.  As a result, an OpenGL window
@@ -938,7 +962,7 @@ make_style(bool fullscreen) {
 
 
   DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
   DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
 
 
-  if (fullscreen){
+  if (_properties.get_fullscreen()) {
     window_style |= WS_SYSMENU;
     window_style |= WS_SYSMENU;
   } else if (!_properties.get_undecorated()) {
   } else if (!_properties.get_undecorated()) {
     window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
     window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
@@ -1015,8 +1039,8 @@ calculate_metrics(bool fullscreen, DWORD window_style, WINDOW_METRICS &metrics,
  * Creates a regular or fullscreen window.
  * Creates a regular or fullscreen window.
  */
  */
 bool WinGraphicsWindow::
 bool WinGraphicsWindow::
-open_graphic_window(bool fullscreen) {
-  DWORD window_style = make_style(fullscreen);
+open_graphic_window() {
+  DWORD window_style = make_style(_properties);
 
 
   wstring title;
   wstring title;
   if (_properties.has_title()) {
   if (_properties.has_title()) {
@@ -2186,12 +2210,12 @@ update_cursor_window(WinGraphicsWindow *to_window) {
     // We are leaving a graphics window; we should restore the Win2000
     // We are leaving a graphics window; we should restore the Win2000
     // effects.
     // effects.
     if (_got_saved_params) {
     if (_got_saved_params) {
-      SystemParametersInfo(SPI_SETMOUSETRAILS, 0,
-                           (PVOID)_saved_mouse_trails, 0);
+      SystemParametersInfo(SPI_SETMOUSETRAILS, _saved_mouse_trails,
+                           0, 0);
       SystemParametersInfo(SPI_SETCURSORSHADOW, 0,
       SystemParametersInfo(SPI_SETCURSORSHADOW, 0,
-                           (PVOID)_saved_cursor_shadow, 0);
+                           _saved_cursor_shadow ? (PVOID)1 : nullptr, 0);
       SystemParametersInfo(SPI_SETMOUSEVANISH, 0,
       SystemParametersInfo(SPI_SETMOUSEVANISH, 0,
-                           (PVOID)_saved_mouse_vanish, 0);
+                           _saved_mouse_vanish ? (PVOID)1 : nullptr, 0);
       _got_saved_params = false;
       _got_saved_params = false;
     }
     }
 
 

+ 2 - 2
panda/src/windisplay/winGraphicsWindow.h

@@ -119,7 +119,7 @@ protected:
   virtual bool calculate_metrics(bool fullscreen, DWORD style,
   virtual bool calculate_metrics(bool fullscreen, DWORD style,
                                  WINDOW_METRICS &metrics, bool &has_origin);
                                  WINDOW_METRICS &metrics, bool &has_origin);
 
 
-  virtual DWORD make_style(bool fullscreen);
+  DWORD make_style(const WindowProperties &properties);
 
 
   virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size,
   virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size,
                                           DWORD &bitdepth);
                                           DWORD &bitdepth);
@@ -127,7 +127,7 @@ protected:
   virtual void support_overlay_window(bool flag);
   virtual void support_overlay_window(bool flag);
 
 
 private:
 private:
-  bool open_graphic_window(bool fullscreen);
+  bool open_graphic_window();
   void adjust_z_order();
   void adjust_z_order();
   void adjust_z_order(WindowProperties::ZOrder last_z_order,
   void adjust_z_order(WindowProperties::ZOrder last_z_order,
                       WindowProperties::ZOrder this_z_order);
                       WindowProperties::ZOrder this_z_order);

+ 4 - 1
samples/shader-terrain/main.py

@@ -34,7 +34,10 @@ class ShaderTerrainDemo(ShowBase):
 
 
         # Set a heightfield, the heightfield should be a 16-bit png and
         # Set a heightfield, the heightfield should be a 16-bit png and
         # have a quadratic size of a power of two.
         # have a quadratic size of a power of two.
-        self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png")
+        heightfield = self.loader.loadTexture("heightfield.png")
+        heightfield.wrap_u = SamplerState.WM_clamp
+        heightfield.wrap_v = SamplerState.WM_clamp
+        self.terrain_node.heightfield = heightfield
 
 
         # Set the target triangle width. For a value of 10.0 for example,
         # Set the target triangle width. For a value of 10.0 for example,
         # the terrain will attempt to make every triangle 10 pixels wide on screen.
         # the terrain will attempt to make every triangle 10 pixels wide on screen.

+ 293 - 0
tests/display/test_color_buffer.py

@@ -0,0 +1,293 @@
+from panda3d import core
+import pytest
+
+TEST_COLOR = core.LColor(1, 127/255.0, 0, 127/255.0)
+TEST_COLOR_SCALE = core.LVecBase4(0.5, 0.5, 0.5, 0.5)
+TEST_SCALED_COLOR = core.LColor(TEST_COLOR)
+TEST_SCALED_COLOR.componentwise_mult(TEST_COLOR_SCALE)
+FUZZ = 0.02
+
+
[email protected](scope='session', params=[False, True], ids=["shader:off", "shader:auto"])
+def shader_attrib(request):
+    """Returns two ShaderAttribs: one with auto shader, one without."""
+    if request.param:
+        return core.ShaderAttrib.make_default().set_shader_auto(True)
+    else:
+        return core.ShaderAttrib.make_off()
+
+
[email protected](scope='session', params=["mat:off", "mat:empty", "mat:amb", "mat:diff", "mat:both"])
+def material_attrib(request):
+    """Returns two MaterialAttribs: one with material, one without.  It
+    shouldn't really matter what we set them to, since the tests in here do
+    not use lighting, and therefore the material should be ignored."""
+
+    if request.param == "mat:off":
+        return core.MaterialAttrib.make_off()
+
+    elif request.param == "mat:empty":
+        return core.MaterialAttrib.make(core.Material())
+
+    elif request.param == "mat:amb":
+        mat = core.Material()
+        mat.ambient = (0.1, 1, 0.5, 1)
+        return core.MaterialAttrib.make(mat)
+
+    elif request.param == "mat:diff":
+        mat = core.Material()
+        mat.diffuse = (0.1, 1, 0.5, 1)
+        return core.MaterialAttrib.make(mat)
+
+    elif request.param == "mat:both":
+        mat = core.Material()
+        mat.diffuse = (0.1, 1, 0.5, 1)
+        mat.ambient = (0.1, 1, 0.5, 1)
+        return core.MaterialAttrib.make(mat)
+
+
[email protected](scope='module', params=[False, True], ids=["srgb:off", "srgb:on"])
+def color_region(request, graphics_pipe):
+    """Creates and returns a DisplayRegion with a depth buffer."""
+
+    engine = core.GraphicsEngine()
+    engine.set_threading_model("")
+
+    host_fbprops = core.FrameBufferProperties()
+    host_fbprops.force_hardware = True
+
+    host = engine.make_output(
+        graphics_pipe,
+        'host',
+        0,
+        host_fbprops,
+        core.WindowProperties.size(32, 32),
+        core.GraphicsPipe.BF_refuse_window,
+    )
+    engine.open_windows()
+
+    if host is None:
+        pytest.skip("GraphicsPipe cannot make offscreen buffers")
+
+    fbprops = core.FrameBufferProperties()
+    fbprops.force_hardware = True
+    fbprops.set_rgba_bits(8, 8, 8, 8)
+    fbprops.srgb_color = request.param
+
+    buffer = engine.make_output(
+        graphics_pipe,
+        'buffer',
+        0,
+        fbprops,
+        core.WindowProperties.size(32, 32),
+        core.GraphicsPipe.BF_refuse_window,
+        host.gsg,
+        host
+    )
+    engine.open_windows()
+
+    if buffer is None:
+        pytest.skip("Cannot make color buffer")
+
+    if fbprops.srgb_color != buffer.get_fb_properties().srgb_color:
+        pytest.skip("Cannot make buffer with required srgb_color setting")
+
+    buffer.set_clear_color_active(True)
+    buffer.set_clear_color((0, 0, 0, 1))
+
+    yield buffer.make_display_region()
+
+    if buffer is not None:
+        engine.remove_window(buffer)
+
+
+def render_color_pixel(region, state, vertex_color=None):
+    """Renders a fragment using the specified render settings, and returns the
+    resulting color value."""
+
+    # Set up the scene with a blank card rendering at specified distance.
+    scene = core.NodePath("root")
+    scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
+
+    camera = scene.attach_new_node(core.Camera("camera"))
+    camera.node().get_lens(0).set_near_far(1, 3)
+    camera.node().set_cull_bounds(core.OmniBoundingVolume())
+
+    if vertex_color is not None:
+        format = core.GeomVertexFormat.get_v3cp()
+    else:
+        format = core.GeomVertexFormat.get_v3()
+
+    vdata = core.GeomVertexData("card", format, core.Geom.UH_static)
+    vdata.unclean_set_num_rows(4)
+
+    vertex = core.GeomVertexWriter(vdata, "vertex")
+    vertex.set_data3(core.Vec3.rfu(-1, 0, 1))
+    vertex.set_data3(core.Vec3.rfu(-1, 0, -1))
+    vertex.set_data3(core.Vec3.rfu(1, 0, 1))
+    vertex.set_data3(core.Vec3.rfu(1, 0, -1))
+
+    if vertex_color is not None:
+        color = core.GeomVertexWriter(vdata, "color")
+        color.set_data4(vertex_color)
+        color.set_data4(vertex_color)
+        color.set_data4(vertex_color)
+        color.set_data4(vertex_color)
+
+    strip = core.GeomTristrips(core.Geom.UH_static)
+    strip.set_shade_model(core.Geom.SM_uniform)
+    strip.add_next_vertices(4)
+    strip.close_primitive()
+
+    geom = core.Geom(vdata)
+    geom.add_primitive(strip)
+
+    gnode = core.GeomNode("card")
+    gnode.add_geom(geom, state)
+    card = scene.attach_new_node(gnode)
+    card.set_pos(0, 2, 0)
+    card.set_scale(60)
+
+    region.active = True
+    region.camera = camera
+
+    color_texture = core.Texture("color")
+    region.window.add_render_texture(color_texture,
+                                     core.GraphicsOutput.RTM_copy_ram,
+                                     core.GraphicsOutput.RTP_color)
+
+    region.window.engine.render_frame()
+    region.window.clear_render_textures()
+
+    col = core.LColor()
+    color_texture.peek().lookup(col, 0.5, 0.5)
+    return col
+
+
+def test_color_write_mask(color_region):
+    state = core.RenderState.make(
+        core.ColorWriteAttrib.make(core.ColorWriteAttrib.C_green),
+    )
+    result = render_color_pixel(color_region, state)
+    assert result == (0, 1, 0, 1)
+
+
+def test_color_empty(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result == (1, 1, 1, 1)
+
+
+def test_color_off(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_off(),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result == (1, 1, 1, 1)
+
+
+def test_color_flat(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_flat(TEST_COLOR),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result.almost_equal(TEST_COLOR, FUZZ)
+
+
+def test_color_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_vertex(),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result.almost_equal(TEST_COLOR, FUZZ)
+
+
+def test_color_empty_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result.almost_equal(TEST_COLOR, FUZZ)
+
+
+def test_color_off_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_off(),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result == (1, 1, 1, 1)
+
+
+def test_scaled_color_empty(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result == (1, 1, 1, 1)
+
+
+def test_scaled_color_off(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_off(),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result == (1, 1, 1, 1)
+
+
+def test_scaled_color_flat(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_flat(TEST_COLOR),
+        core.ColorScaleAttrib.make(TEST_COLOR_SCALE),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state)
+    assert result.almost_equal(TEST_SCALED_COLOR, FUZZ)
+
+
+def test_scaled_color_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_vertex(),
+        core.ColorScaleAttrib.make(TEST_COLOR_SCALE),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result.almost_equal(TEST_SCALED_COLOR, FUZZ)
+
+
+def test_scaled_color_empty_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorScaleAttrib.make(TEST_COLOR_SCALE),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result.almost_equal(TEST_SCALED_COLOR, FUZZ)
+
+
+def test_scaled_color_off_vertex(color_region, shader_attrib, material_attrib):
+    state = core.RenderState.make(
+        core.ColorAttrib.make_off(),
+        core.ColorScaleAttrib.make(TEST_COLOR_SCALE),
+        shader_attrib,
+        material_attrib,
+    )
+    result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR)
+    assert result.almost_equal(TEST_COLOR_SCALE, FUZZ)
+

+ 0 - 2
tests/display/test_depth_buffer.py

@@ -91,8 +91,6 @@ def render_depth_pixel(region, distance, near, far, clear=None, write=True):
     region.window.engine.render_frame()
     region.window.engine.render_frame()
     region.window.clear_render_textures()
     region.window.clear_render_textures()
 
 
-    depth_texture.write("test2.png")
-
     col = core.LColor()
     col = core.LColor()
     depth_texture.peek().lookup(col, 0.5, 0.5)
     depth_texture.peek().lookup(col, 0.5, 0.5)
     return col[0]
     return col[0]

+ 101 - 0
tests/dtoolutil/test_textencoder.py

@@ -0,0 +1,101 @@
+import sys
+import pytest
+from panda3d.core import TextEncoder
+
+if sys.version_info >= (3, 0):
+    unichr = chr
+    xrange = range
+
+
+def valid_characters():
+    """Generator yielding all valid Unicode code points."""
+
+    for i in xrange(0xd800):
+        yield unichr(i)
+
+    for i in xrange(0xe000, sys.maxunicode + 1):
+        if i != 0xfeff and i & 0xfffe != 0xfffe:
+            yield unichr(i)
+
+
+def test_text_decode_iso8859():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_iso8859)
+
+    for i in xrange(255):
+        enc = unichr(i).encode('latin-1')
+        assert len(enc) == 1
+
+        dec = encoder.decode_text(enc)
+        assert len(dec) == 1
+        assert ord(dec) == i
+
+
+def test_text_decode_utf8():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_utf8)
+
+    for c in valid_characters():
+        enc = c.encode('utf-8')
+        assert len(enc) <= 4
+
+        dec = encoder.decode_text(enc)
+        assert len(dec) == 1
+        assert dec == c
+
+
+def test_text_decode_utf16be():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_utf16be)
+
+    for c in valid_characters():
+        enc = c.encode('utf-16be')
+
+        dec = encoder.decode_text(enc)
+        assert len(c) == len(dec)
+        assert c == dec
+
+
+def test_text_encode_iso8859():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_iso8859)
+
+    for i in xrange(255):
+        c = unichr(i)
+        enc = encoder.encode_wtext(c)
+        assert enc == c.encode('latin-1')
+
+
+def test_text_encode_utf8():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_utf8)
+
+    for c in valid_characters():
+        enc = encoder.encode_wtext(c)
+        assert enc == c.encode('utf-8')
+
+
+def test_text_encode_utf16be():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_utf16be)
+
+    for c in valid_characters():
+        enc = encoder.encode_wtext(c)
+        assert enc == c.encode('utf-16-be')
+
+
+def test_text_append_unicode_char():
+    encoder = TextEncoder()
+    encoder.set_encoding(TextEncoder.E_iso8859)
+
+    code_points = []
+    for code_point in [0, 1, 127, 128, 255, 256, 0xfffd, 0x10000, 0x10ffff]:
+        if code_point <= sys.maxunicode:
+            code_points.append(code_point)
+            encoder.append_unicode_char(code_point)
+
+    encoded = encoder.get_wtext()
+    assert len(encoded) == len(code_points)
+
+    for a, b in zip(code_points, encoded):
+        assert a == ord(b)

+ 18 - 16
tests/interrogate/test_property.py

@@ -2,7 +2,11 @@ import sys
 import pytest
 import pytest
 from panda3d import core
 from panda3d import core
 from contextlib import contextmanager
 from contextlib import contextmanager
-import collections
+
+if sys.version_info >= (3, 3):
+    import collections.abc as collections_abc
+else:
+    import _abcoll as collections_abc
 
 
 
 
 @contextmanager
 @contextmanager
@@ -52,7 +56,6 @@ def test_property2():
 
 
 
 
 # The next tests are for MAKE_SEQ_PROPERTY.
 # The next tests are for MAKE_SEQ_PROPERTY.
[email protected]
 def seq_property(*items):
 def seq_property(*items):
     """ Returns a sequence property initialized with the given items. """
     """ Returns a sequence property initialized with the given items. """
 
 
@@ -73,11 +76,11 @@ item_c = core.CollisionSphere((0, 0, 0), 3)
 
 
 def test_seq_property_abc():
 def test_seq_property_abc():
     prop = seq_property()
     prop = seq_property()
-    assert isinstance(prop, collections.Container)
-    assert isinstance(prop, collections.Sized)
-    assert isinstance(prop, collections.Iterable)
-    assert isinstance(prop, collections.MutableSequence)
-    assert isinstance(prop, collections.Sequence)
+    assert isinstance(prop, collections_abc.Container)
+    assert isinstance(prop, collections_abc.Sized)
+    assert isinstance(prop, collections_abc.Iterable)
+    assert isinstance(prop, collections_abc.MutableSequence)
+    assert isinstance(prop, collections_abc.Sequence)
 
 
 
 
 def test_seq_property_empty():
 def test_seq_property_empty():
@@ -411,7 +414,6 @@ def test_seq_property_extend():
 
 
 
 
 # The next tests are for MAKE_MAP_PROPERTY.
 # The next tests are for MAKE_MAP_PROPERTY.
[email protected]
 def map_property(**items):
 def map_property(**items):
     """ Returns a mapping property initialized with the given values. """
     """ Returns a mapping property initialized with the given values. """
 
 
@@ -425,11 +427,11 @@ def map_property(**items):
 
 
 def test_map_property_abc():
 def test_map_property_abc():
     prop = map_property()
     prop = map_property()
-    assert isinstance(prop, collections.Container)
-    assert isinstance(prop, collections.Sized)
-    assert isinstance(prop, collections.Iterable)
-    assert isinstance(prop, collections.MutableMapping)
-    assert isinstance(prop, collections.Mapping)
+    assert isinstance(prop, collections_abc.Container)
+    assert isinstance(prop, collections_abc.Sized)
+    assert isinstance(prop, collections_abc.Iterable)
+    assert isinstance(prop, collections_abc.MutableMapping)
+    assert isinstance(prop, collections_abc.Mapping)
 
 
 
 
 def test_map_property_empty():
 def test_map_property_empty():
@@ -607,19 +609,19 @@ def test_map_property_update():
 def test_map_property_keys():
 def test_map_property_keys():
     prop = map_property(key='value', key2='value2')
     prop = map_property(key='value', key2='value2')
 
 
-    assert isinstance(prop.keys(), collections.MappingView)
+    assert isinstance(prop.keys(), collections_abc.MappingView)
     assert frozenset(prop.keys()) == frozenset(('key', 'key2'))
     assert frozenset(prop.keys()) == frozenset(('key', 'key2'))
 
 
 
 
 def test_map_property_values():
 def test_map_property_values():
     prop = map_property(key='value', key2='value2')
     prop = map_property(key='value', key2='value2')
 
 
-    assert isinstance(prop.values(), collections.ValuesView)
+    assert isinstance(prop.values(), collections_abc.ValuesView)
     assert frozenset(prop.values()) == frozenset(('value', 'value2'))
     assert frozenset(prop.values()) == frozenset(('value', 'value2'))
 
 
 
 
 def test_map_property_items():
 def test_map_property_items():
     prop = map_property(key='value', key2='value2')
     prop = map_property(key='value', key2='value2')
 
 
-    assert isinstance(prop.items(), collections.MappingView)
+    assert isinstance(prop.items(), collections_abc.MappingView)
     assert frozenset(prop.items()) == frozenset((('key', 'value'), ('key2', 'value2')))
     assert frozenset(prop.items()) == frozenset((('key', 'value'), ('key2', 'value2')))

+ 106 - 0
tests/text/test_textnode.py

@@ -0,0 +1,106 @@
+from panda3d import core
+
+
+def test_textnode_card_as_margin():
+    text = core.TextNode("test")
+    text.text = "Test"
+
+    l, r, b, t = 0.1, 0.2, 0.3, 0.4
+    text.set_card_as_margin(l, r, b, t)
+
+    assert text.has_card()
+    assert text.is_card_as_margin()
+    assert text.get_card_as_set() == (l, r, b, t)
+
+    card_actual = text.get_card_actual()
+    card_expect = core.LVecBase4(
+        text.get_left() - l,
+        text.get_right() + r,
+        text.get_bottom() - b,
+        text.get_top() + t)
+    assert card_actual == card_expect
+
+
+def test_textnode_card_actual():
+    text = core.TextNode("test")
+    text.text = "Test"
+
+    l, r, b, t = 0.1, 0.2, 0.3, 0.4
+    text.set_card_actual(l, r, b, t)
+
+    assert text.has_card()
+    assert not text.is_card_as_margin()
+    assert text.get_card_as_set() == (l, r, b, t)
+
+    card_actual = text.get_card_actual()
+    card_expect = core.LVecBase4(l, r, b, t)
+    assert card_actual == card_expect
+
+
+def test_textnode_frame_as_margin():
+    text = core.TextNode("test")
+    text.text = "Test"
+
+    l, r, b, t = 0.1, 0.2, 0.3, 0.4
+    text.set_frame_as_margin(l, r, b, t)
+
+    assert text.has_frame()
+    assert text.is_frame_as_margin()
+    assert text.get_frame_as_set() == (l, r, b, t)
+
+    frame_actual = text.get_frame_actual()
+    frame_expect = core.LVecBase4(
+        text.get_left() - l,
+        text.get_right() + r,
+        text.get_bottom() - b,
+        text.get_top() + t)
+    assert frame_actual == frame_expect
+
+
+def test_textnode_frame_actual():
+    text = core.TextNode("test")
+    text.text = "Test"
+
+    l, r, b, t = 0.1, 0.2, 0.3, 0.4
+    text.set_frame_actual(l, r, b, t)
+
+    assert text.has_frame()
+    assert not text.is_frame_as_margin()
+    assert text.get_frame_as_set() == (l, r, b, t)
+
+    frame_actual = text.get_frame_actual()
+    frame_expect = core.LVecBase4(l, r, b, t)
+    assert frame_actual == frame_expect
+
+
+def test_textnode_flatten_color():
+    text = core.TextNode("test")
+    text.text_color = (0, 0, 0, 1)
+    path = core.NodePath(text)
+
+    color = core.LColor(1, 0, 0, 1)
+    path.set_color(color)
+    path.flatten_strong()
+
+    assert text.text_color.almost_equal(color)
+    assert text.shadow_color.almost_equal(color)
+    assert text.frame_color.almost_equal(color)
+    assert text.card_color.almost_equal(color)
+
+
+def test_textnode_flatten_colorscale():
+    text = core.TextNode("test")
+    text.text_color = (1, 0, 0, 0)
+    text.shadow_color = (0, 1, 0, 0)
+    text.frame_color = (0, 0, 1, 0)
+    text.card_color = (0, 0, 0, 1)
+    path = core.NodePath(text)
+
+    color = core.LColor(.5, .5, .5, .5)
+    path.set_color_scale(color)
+    path.flatten_strong()
+
+    assert text.text_color.almost_equal((.5, 0, 0, 0))
+    assert text.shadow_color.almost_equal((0, .5, 0, 0))
+    assert text.frame_color.almost_equal((0, 0, .5, 0))
+    assert text.card_color.almost_equal((0, 0, 0, .5))