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
 -------
 
-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
 [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
 
 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
-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
@@ -101,7 +103,7 @@ If you are on Ubuntu, this command should cover the most frequently
 used third-party packages:
 
 ```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
@@ -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
 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
 =============
 

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

@@ -15,9 +15,6 @@
 #include "typeRegistryNode.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
  * 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:
   constexpr TypeHandle(int index);
 
-  // Only kept temporarily for ABI compatibility.
-  static TypeHandle _none;
-
   int _index;
   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);
 }
 
-/**
- *
- */
-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.
  */

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

@@ -52,12 +52,15 @@ PUBLISHED:
     Files _files;
   };
 
-  DSearchPath();
+  DSearchPath() = default;
   DSearchPath(const std::string &path, const std::string &separator = std::string());
   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 append_directory(const Filename &directory);

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

@@ -1,2 +1,3 @@
 #include "filename_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::
-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.
  */
-int StringDecoder::
+char32_t StringDecoder::
 get_next_character() {
   if (test_eof()) {
     return -1;
@@ -57,19 +57,20 @@ get_notify_ptr() {
 
 /*
 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
 sequences (where the "free bits" shown by x's in the table are
 combined in the order shown, and interpreted from most significant to
 least significant):
 
  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:
 
@@ -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.
  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.
+ F0 to F7 hex (240 to 247):  first byte of a four-byte sequence.
 */
 
 /**
  * Returns the next character in sequence.
  */
-int StringUtf8Decoder::
+char32_t StringUtf8Decoder::
 get_next_character() {
   unsigned int result;
   while (!test_eof()) {
@@ -125,6 +127,35 @@ get_next_character() {
       unsigned int three = (unsigned char)_input[_p++];
       result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
       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
@@ -144,7 +175,7 @@ get_next_character() {
 /**
  * Returns the next character in sequence.
  */
-int StringUnicodeDecoder::
+char32_t StringUtf16Decoder::
 get_next_character() {
   if (test_eof()) {
     return -1;
@@ -159,5 +190,33 @@ get_next_character() {
     return -1;
   }
   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);
   virtual ~StringDecoder();
 
-  virtual int get_next_character();
+  virtual char32_t get_next_character();
   INLINE bool is_eof();
 
   static void set_notify_ptr(std::ostream *ptr);
@@ -48,20 +48,23 @@ class StringUtf8Decoder : public StringDecoder {
 public:
   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
- * character sequence.
+ * character sequence.  It supports surrogate pairs.
  */
-class StringUnicodeDecoder : public StringDecoder {
+class StringUtf16Decoder : public StringDecoder {
 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"
 
 #endif

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

@@ -90,6 +90,7 @@ set_text(const std::string &text) {
   if (!has_text() || _text != text) {
     _text = text;
     _flags = (_flags | F_got_text) & ~F_got_wtext;
+    text_changed();
   }
 }
 
@@ -101,7 +102,11 @@ set_text(const std::string &text) {
  */
 INLINE void TextEncoder::
 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();
   _wtext = std::wstring();
   _flags |= (F_got_text | F_got_wtext);
+  text_changed();
 }
 
 /**
@@ -151,8 +157,11 @@ get_text(TextEncoder::Encoding encoding) const {
  */
 INLINE void TextEncoder::
 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.
  */
 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);
+#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;
+  text_changed();
 }
 
 /**
@@ -200,6 +225,7 @@ set_unicode_char(size_t index, int character) {
   if (index < _wtext.length()) {
     _wtext[index] = character;
     _flags &= ~F_got_text;
+    text_changed();
   }
 }
 
@@ -418,6 +444,7 @@ set_wtext(const std::wstring &wtext) {
   if (!has_text() || _wtext != wtext) {
     _wtext = wtext;
     _flags = (_flags | F_got_wtext) & ~F_got_text;
+    text_changed();
   }
 }
 
@@ -439,8 +466,11 @@ get_wtext() const {
  */
 INLINE void TextEncoder::
 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::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
@@ -35,6 +35,7 @@ make_upper() {
     (*si) = unicode_toupper(*si);
   }
   _flags &= ~F_got_text;
+  text_changed();
 }
 
 /**
@@ -49,6 +50,7 @@ make_lower() {
     (*si) = unicode_tolower(*si);
   }
   _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::
-encode_wchar(wchar_t ch, TextEncoder::Encoding encoding) {
+encode_wchar(char32_t ch, TextEncoder::Encoding encoding) {
   switch (encoding) {
   case E_iso8859:
     if ((ch & ~0xff) == 0) {
@@ -143,17 +145,38 @@ encode_wchar(wchar_t ch, TextEncoder::Encoding encoding) {
       return
         string(1, (char)((ch >> 6) | 0xc0)) +
         string(1, (char)((ch & 0x3f) | 0x80));
-    } else {
+    } else if ((ch & ~0xffff) == 0) {
       return
         string(1, (char)((ch >> 12) | 0xe0)) +
         string(1, (char)(((ch >> 6) & 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 "";
@@ -167,8 +190,25 @@ string TextEncoder::
 encode_wtext(const wstring &wtext, TextEncoder::Encoding encoding) {
   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;
@@ -187,9 +227,9 @@ decode_text(const string &text, TextEncoder::Encoding encoding) {
       return decode_text_impl(decoder);
     }
 
-  case E_unicode:
+  case E_utf16be:
     {
-      StringUnicodeDecoder decoder(text);
+      StringUtf16Decoder decoder(text);
       return decode_text_impl(decoder);
     }
 
@@ -211,7 +251,7 @@ decode_text_impl(StringDecoder &decoder) {
   wstring result;
   // bool expand_amp = get_expand_amp();
 
-  wchar_t character = decoder.get_next_character();
+  char32_t character = decoder.get_next_character();
   while (!decoder.is_eof()) {
     /*
     if (character == '&' && expand_amp) {
@@ -219,7 +259,14 @@ decode_text_impl(StringDecoder &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();
   }
 
@@ -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:
     return out << "utf8";
 
-  case TextEncoder::E_unicode:
-    return out << "unicode";
+  case TextEncoder::E_utf16be:
+    return out << "utf16be";
   };
 
   return out << "**invalid TextEncoder::Encoding(" << (int)encoding << ")**";
@@ -346,8 +399,9 @@ operator >> (istream &in, TextEncoder::Encoding &encoding) {
     encoding = TextEncoder::E_iso8859;
   } else if (word == "utf8" || word == "utf-8") {
     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 {
     ostream *notify_ptr = StringDecoder::get_notify_ptr();
     if (notify_ptr != nullptr) {

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

@@ -35,12 +35,17 @@ PUBLISHED:
   enum Encoding {
     E_iso8859,
     E_utf8,
-    E_unicode
+    E_utf16be,
+
+    // Deprecated alias for E_utf16be
+    E_unicode = E_utf16be,
   };
 
   INLINE TextEncoder();
   INLINE TextEncoder(const TextEncoder &copy);
 
+  virtual ~TextEncoder() = default;
+
   INLINE void set_encoding(Encoding encoding);
   INLINE Encoding get_encoding() const;
 
@@ -48,18 +53,29 @@ PUBLISHED:
   INLINE static Encoding get_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, Encoding encoding);
+#endif
   INLINE void clear_text();
   INLINE bool has_text() const;
 
   void make_upper();
   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(Encoding encoding) const;
   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 int get_unicode_char(size_t index) const;
   INLINE void set_unicode_char(size_t index, int character);
@@ -91,11 +107,24 @@ PUBLISHED:
   std::wstring get_wtext_as_ascii() 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;
   static std::string encode_wtext(const std::wstring &wtext, Encoding encoding);
   INLINE std::wstring decode_text(const std::string &text) const;
   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:
   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.
   bool force_publish = false;
   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) {
     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'",
     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;
 }
 

+ 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 "pstrtod.h"
 #include "string_utils.h"
+#include "executionEnvironment.h"
+#include "mutexImpl.h"
 
 using std::string;
 
@@ -131,6 +133,41 @@ set_double_word(size_t n, double value) {
   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 "vector_string.h"
 #include "numeric_types.h"
+#include "filename.h"
 
 #include <vector>
 
@@ -68,6 +69,8 @@ PUBLISHED:
   void set_int64_word(size_t n, int64_t value);
   void set_double_word(size_t n, double value);
 
+  Filename get_filename_value() const;
+
   INLINE int get_decl_seq() const;
 
   void output(std::ostream &out) const;

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

@@ -18,6 +18,18 @@
  */
 void ConfigVariableBool::
 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.
   if (!is_cache_valid(_local_modified)) {
     nassertv(_core != nullptr);
-
     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);
   }
   lock.unlock();

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

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

+ 5 - 0
makepanda/confauto.in

@@ -21,6 +21,11 @@
 
 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
 # used by the MovieVideo and MovieAudio code to determine which module
 # 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':
         LibName("ALWAYS", "-framework AppKit")
-        if (PkgSkip("OPENCV")==0):
-            LibName("OPENCV", "-framework QuickTime")
         LibName("AGL", "-framework AGL")
         LibName("CARBON", "-framework Carbon")
         LibName("COCOA", "-framework Cocoa")
@@ -1398,9 +1396,10 @@ def CompileCxx(obj,src,opts):
                     # Work around Apple compiler bug.
                     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.
-                if optlevel >= 4 or GetTarget() == "android":
+                if optlevel >= 4 or target == "android":
                     cmd += " -fno-rtti"
 
         if ('SSE2' in opts or not PkgSkip("SSE2")) and not arch.startswith("arm") and arch != 'aarch64':
@@ -2979,6 +2978,9 @@ else:
     # otherwise, disable it.
     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")):
     configprc = ReadFile("makepanda/myconfig.in")
 else:
@@ -3645,6 +3647,7 @@ IGATEFILES += [
     "dSearchPath.h",
     "executionEnvironment.h",
     "textEncoder.h",
+    "textEncoder_ext.h",
     "filename.h",
     "filename_ext.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;
 }
 
-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"
 

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

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

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

@@ -593,8 +593,7 @@ remove_all_windows() {
   Windows old_windows;
   old_windows.swap(_windows);
   Windows::iterator wi;
-  for (wi = old_windows.begin(); wi != old_windows.end(); ++wi) {
-    GraphicsOutput *win = (*wi);
+  for (GraphicsOutput *win : old_windows) {
     nassertv(win != nullptr);
     do_remove_window(win, current_thread);
     GraphicsStateGuardian *gsg = win->get_gsg();
@@ -605,6 +604,14 @@ remove_all_windows() {
 
   {
     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();
   }
 

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

@@ -2722,7 +2722,7 @@ do_issue_color_scale() {
   }
 
   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
     // texture.
     _state_mask.clear_bit(TextureAttrib::get_class_slot());
@@ -3168,6 +3168,17 @@ determine_light_color_scale() {
                                 _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 {
     // Otherise, leave the materials alone, but we might still scale the
     // lights.

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

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

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

@@ -12,10 +12,14 @@
  */
 
 #include "standardMunger.h"
-#include "renderState.h"
-#include "graphicsStateGuardian.h"
+
 #include "config_gobj.h"
+
 #include "displayRegion.h"
+#include "graphicsStateGuardian.h"
+#include "lightAttrib.h"
+#include "materialAttrib.h"
+#include "renderState.h"
 
 TypeHandle StandardMunger::_type_handle;
 
@@ -36,7 +40,8 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
   _munge_color(false),
   _munge_color_scale(false),
   _auto_shader(false),
-  _shader_skinning(false)
+  _shader_skinning(false),
+  _remove_material(false)
 {
   const ShaderAttrib *shader_attrib;
   state->get_attrib_def(shader_attrib);
@@ -54,24 +59,10 @@ StandardMunger(GraphicsStateGuardianBase *gsg, const RenderState *state,
     const ColorScaleAttrib *color_scale_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) &&
                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.
     }
   }
+
+  // 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) {
     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);
 }
@@ -344,5 +351,9 @@ munge_state_impl(const RenderState *state) {
     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;
 }

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

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

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

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

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

@@ -10,34 +10,3 @@
  * @author drose
  * @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;
 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 {
 public:
-  INLINE DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state);
+  DXGeomMunger9(GraphicsStateGuardian *gsg, const RenderState *state);
   virtual ~DXGeomMunger9();
   ALLOC_DELETED_CHAIN(DXGeomMunger9);
 

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

@@ -140,6 +140,7 @@ DXGraphicsStateGuardian9(GraphicsEngine *engine, GraphicsPipe *pipe) :
 
   _last_fvf = 0;
   _num_bound_streams = 0;
+  _white_vbuffer = nullptr;
 
   _vertex_shader_version_major = 0;
   _vertex_shader_version_minor = 0;
@@ -798,9 +799,9 @@ clear(DrawableRegion *clearable) {
     main_flags |=  D3DCLEAR_TARGET;
   }
 
-  if (clearable->get_clear_depth_active()) {
+  if (clearable->get_clear_depth_active() &&
+      _screen->_presentation_params.EnableAutoDepthStencil) {
     aux_flags |=  D3DCLEAR_ZBUFFER;
-    nassertv(_screen->_presentation_params.EnableAutoDepthStencil);
   }
 
   if (clearable->get_clear_stencil_active()) {
@@ -4545,6 +4546,11 @@ reset_d3d_device(D3DPRESENT_PARAMETERS *presentation_params,
     release_all_vertex_buffers();
     release_all_index_buffers();
 
+    if (_white_vbuffer != nullptr) {
+      _white_vbuffer->Release();
+      _white_vbuffer = nullptr;
+    }
+
     // must be called before reset
     Thread *current_thread = Thread::get_current_thread();
     _prepared_objects->begin_frame(this, current_thread);
@@ -5404,6 +5410,40 @@ set_cg_device(LPDIRECT3DDEVICE9 cg_device) {
 #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 struct _KEY_ELEMENT

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

@@ -168,6 +168,7 @@ public:
   static void set_cg_device(LPDIRECT3DDEVICE9 cg_device);
   virtual bool get_supports_cg_profile(const std::string &name) const;
 
+  LPDIRECT3DVERTEXBUFFER9 get_white_vbuffer();
 
 protected:
   void do_issue_transform();
@@ -274,12 +275,6 @@ protected:
 
   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 {
     None,
     PerVertexFog=D3DRS_FOGVERTEXMODE,
@@ -320,6 +315,7 @@ protected:
 
   DWORD _last_fvf;
   int _num_bound_streams;
+  LPDIRECT3DVERTEXBUFFER9 _white_vbuffer;
 
   // 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

+ 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
     // out only those for a single stream.
 
+    bool apply_white_color = false;
+
     int number_of_arrays = gsg->_data_reader->get_num_arrays();
     for (int array_index = 0; array_index < number_of_arrays; ++array_index) {
       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;
         Geom::NumericType numeric_type;
         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
           // 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";
+          if (name == InternalName::get_color()) {
+            apply_white_color = true;
+          }
           continue;
         }
 
@@ -564,6 +574,19 @@ update_shader_vertex_arrays(DXShaderContext9 *prev, GSG *gsg, bool force) {
 
     _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 &&
         _vertex_element_array->add_end_vertex_element()) {
       if (dxgsg9_cat.is_debug()) {

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

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

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

@@ -30,7 +30,7 @@
  * connectivity, and generates a set of EggTriangleStrips that represent the
  * same geometry.
  */
-class EggMesher {
+class EXPCL_PANDA_EGG EggMesher {
 public:
   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
  * indices into the same vertex pool.
  */
-class EggMesherEdge {
+class EXPCL_PANDA_EGG EggMesherEdge {
 public:
   INLINE EggMesherEdge(int vi_a, int vi_b);
   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
  * EggTriangleFan out of the polygons connected to the indicated vertex.
  */
-class EggMesherFanMaker {
+class EXPCL_PANDA_EGG EggMesherFanMaker {
 public:
   typedef plist<const EggMesherEdge *> Edges;
   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
  * quad, since that's how strips generally start out.
  */
-class EggMesherStrip {
+class EXPCL_PANDA_EGG EggMesherStrip {
 public:
   enum PrimType {
     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
  * group related LOD children together under a single LOD node.
  */
-class EggBinner : public EggBinMaker {
+class EXPCL_PANDA_EGG2PG EggBinner : public EggBinMaker {
 public:
   // The BinNumber serves to identify why a particular EggBin was created.
   enum BinNumber {

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

@@ -64,7 +64,7 @@ class CharacterMaker;
  *
  * This class isn't exported from this package.
  */
-class EggLoader {
+class EXPCL_PANDA_EGG2PG EggLoader {
 public:
   EggLoader();
   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
  * objects via the EggBinner.
  */
-class EggRenderState : public EggUserData {
+class EXPCL_PANDA_EGG2PG EggRenderState : public EggUserData {
 public:
   INLINE EggRenderState(EggLoader &loader);
   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
  * class).
  */
-class EggSaver {
+class EXPCL_PANDA_EGG2PG EggSaver {
 PUBLISHED:
   EggSaver(EggData *data = nullptr);
 

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

@@ -99,6 +99,27 @@ PUBLISHED:
 
   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:
   void jump_to_task_chain(AsyncTaskManager *manager);
   DoneStatus unlock_and_do_task();

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

@@ -61,9 +61,6 @@ PUBLISHED:
   int __clear__();
 
 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,
   // according to the task manager's clock.
   MAKE_PROPERTY(time, get_elapsed_time);
@@ -88,10 +85,6 @@ PUBLISHED:
   // according to the task manager's clock.
   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
   // custom variables may be stored.
   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) {
     mat_size = sizeof(UnalignedLMatrix4d);
   } else {
-    assert(false);
+    nassertv_always(false);
+    return; // Make sure compiler knows control flow doesn't proceed.
   }
 
   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
 // 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_ushort>;

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

@@ -226,12 +226,14 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte
         if (!resource) {
           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") {
         // 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)
           << " 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
 

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

@@ -283,6 +283,13 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     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());
   report_my_gl_errors();
   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);
     } else
 #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)) {
         if (!setup_array_data(client_pointer, array_reader, force)) {
           return false;
@@ -4409,7 +4410,13 @@ update_standard_vertex_arrays(bool force) {
         glDisableClientState(GL_COLOR_ARRAY);
 
         // 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

+ 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) {
           // Vertex colors are disabled or not present.  Apply flat color.
 #ifdef STDFLOAT_DOUBLE
-          _glgsg->_glVertexAttrib4dv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4dv(p, _glgsg->_scene_graph_color.get_data());
 #else
-          _glgsg->_glVertexAttrib4fv(p, color_attrib->get_color().get_data());
+          _glgsg->_glVertexAttrib4fv(p, _glgsg->_scene_graph_color.get_data());
 #endif
         }
       }

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

@@ -84,7 +84,7 @@ operator = (const TextureStage &other) {
   _combine_rgb_operand2 = other._combine_rgb_operand2;
   _combine_alpha_mode = other._combine_alpha_mode;
   _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_operand1 = other._combine_alpha_operand1;
   _combine_alpha_source2 = other._combine_alpha_source2;

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

@@ -22,6 +22,7 @@
  * @param filename Heightfield texture
  */
 INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
+  MutexHolder holder(_lock);
   _heightfield_tex = heightfield;
 }
 
@@ -33,6 +34,7 @@ INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
  * @return Path to the heightfield
  */
 INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
+  MutexHolder holder(_lock);
   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
  */
 INLINE void ShaderTerrainMesh::set_chunk_size(size_t chunk_size) {
+  MutexHolder holder(_lock);
   _chunk_size = chunk_size;
 }
 
@@ -63,6 +66,7 @@ INLINE void ShaderTerrainMesh::set_chunk_size(size_t chunk_size) {
  * @return Chunk size
  */
 INLINE size_t ShaderTerrainMesh::get_chunk_size() const {
+  MutexHolder holder(_lock);
   return _chunk_size;
 }
 
@@ -81,6 +85,7 @@ INLINE size_t ShaderTerrainMesh::get_chunk_size() const {
  * @param generate_patches [description]
  */
 INLINE void ShaderTerrainMesh::set_generate_patches(bool generate_patches) {
+  MutexHolder holder(_lock);
   _generate_patches = generate_patches;
 }
 
@@ -92,6 +97,7 @@ INLINE void ShaderTerrainMesh::set_generate_patches(bool generate_patches) {
  * @return Whether to generate patches
  */
 INLINE bool ShaderTerrainMesh::get_generate_patches() const {
+  MutexHolder holder(_lock);
   return _generate_patches;
 }
 
@@ -107,6 +113,7 @@ INLINE bool ShaderTerrainMesh::get_generate_patches() const {
  * @param target_triangle_width Desired triangle width in pixels
  */
 INLINE void ShaderTerrainMesh::set_target_triangle_width(PN_stdfloat target_triangle_width) {
+  MutexHolder holder(_lock);
   _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
  */
 INLINE PN_stdfloat ShaderTerrainMesh::get_target_triangle_width() const {
+  MutexHolder holder(_lock);
   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
  */
 INLINE void ShaderTerrainMesh::set_update_enabled(bool update_enabled) {
+  MutexHolder holder(_lock);
   _update_enabled = update_enabled;
 }
 
@@ -142,6 +151,7 @@ INLINE void ShaderTerrainMesh::set_update_enabled(bool update_enabled) {
  * @return Whether to update the terrain
  */
 INLINE bool ShaderTerrainMesh::get_update_enabled() const {
+  MutexHolder holder(_lock);
   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
  */
 bool ShaderTerrainMesh::generate() {
+  MutexHolder holder(_lock);
   if (!do_check_heightfield())
     return false;
 
@@ -461,6 +462,7 @@ bool ShaderTerrainMesh::safe_to_combine() const {
  * @copydoc PandaNode::add_for_draw()
  */
 void ShaderTerrainMesh::add_for_draw(CullTraverser *trav, CullTraverserData &data) {
+  MutexHolder holder(_lock);
 
   // Make sure the terrain was properly initialized, and the geom was created
   // successfully
@@ -711,6 +713,7 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
  * @return World-Space point
  */
 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->has_ram_image(), LPoint3(0)); // Heightfield not in memory
 

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

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

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

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

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

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

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

@@ -64,7 +64,7 @@ private:
   CGGammaValue _gOriginalRedTable[ 256 ];
   CGGammaValue _gOriginalGreenTable[ 256 ];
   CGGammaValue _gOriginalBlueTable[ 256 ];
-  CGTableCount _sampleCount;
+  uint32_t _sampleCount;
   CGDisplayErr _cgErr;
 
 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<<"_alive "<<_alive<<"\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);
   #endif //] NDEBUG
 }

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

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

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

@@ -68,7 +68,7 @@ make_off() {
  */
 CPT(RenderAttrib) ColorAttrib::
 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.
  */
 void ColorAttrib::
 quantize_color() {
   switch (_type) {
   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;
 
   case T_off:

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

@@ -88,7 +88,7 @@ public:
     register_type(_type_handle, "ColorAttrib",
                   RenderAttrib::get_class_type());
     _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 {
     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.
  */
 void ColorScaleAttrib::
 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) {
     // 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();
     for (int i = 0; i < num_dirs; ++i) {
       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
 
-#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_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 synthesize_collector("*:Munge:ShaderGen:Synthesize");
 
@@ -399,11 +407,12 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
       if (stage->get_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_source1(), stage->get_combine_rgb_operand1(),
         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_source1(), stage->get_combine_alpha_operand1(),
         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();
 }
 
+#undef PAUSE
+
 #endif  // MUTEX_SPINLOCK

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

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

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

@@ -33,6 +33,7 @@ get_line_height() const {
  */
 INLINE void TextNode::
 set_max_rows(int max_rows) {
+  MutexHolder holder(_lock);
   _max_rows = max_rows;
   invalidate_with_measure();
 }
@@ -43,6 +44,7 @@ set_max_rows(int max_rows) {
  */
 INLINE void TextNode::
 clear_max_rows() {
+  MutexHolder holder(_lock);
   _max_rows = 0;
   invalidate_with_measure();
 }
@@ -53,6 +55,7 @@ clear_max_rows() {
  */
 INLINE bool TextNode::
 has_max_rows() const {
+  MutexHolder holder(_lock);
   return _max_rows > 0;
 }
 
@@ -62,6 +65,7 @@ has_max_rows() const {
  */
 INLINE int TextNode::
 get_max_rows() const {
+  MutexHolder holder(_lock);
   return _max_rows;
 }
 
@@ -71,6 +75,7 @@ get_max_rows() const {
  */
 INLINE bool TextNode::
 has_overflow() const {
+  MutexHolder holder(_lock);
   check_measure();
   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::
 set_frame_color(const LColor &frame_color) {
+  MutexHolder holder(_lock);
   if (_frame_color != frame_color) {
     _frame_color = frame_color;
     invalidate_no_measure();
@@ -99,6 +105,7 @@ set_frame_color(const LColor &frame_color) {
  */
 INLINE LColor TextNode::
 get_frame_color() const {
+  MutexHolder holder(_lock);
   return _frame_color;
 }
 
@@ -107,7 +114,8 @@ get_frame_color() const {
  */
 INLINE void TextNode::
 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;
     _card_border_size = size;
     _card_border_uv_portion = uv_portion;
@@ -120,7 +128,8 @@ set_card_border(PN_stdfloat size, PN_stdfloat uv_portion) {
  */
 INLINE void TextNode::
 clear_card_border() {
-  if (has_card_border()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_card_border) {
     _flags &= ~F_has_card_border;
     invalidate_no_measure();
   }
@@ -131,6 +140,7 @@ clear_card_border() {
  */
 INLINE PN_stdfloat TextNode::
 get_card_border_size() const {
+  MutexHolder holder(_lock);
   return _card_border_size;
 }
 
@@ -139,6 +149,7 @@ get_card_border_size() const {
  */
 INLINE PN_stdfloat TextNode::
 get_card_border_uv_portion() const {
+  MutexHolder holder(_lock);
   return _card_border_uv_portion;
 }
 
@@ -147,6 +158,7 @@ get_card_border_uv_portion() const {
  */
 INLINE bool TextNode::
 has_card_border() const {
+  MutexHolder holder(_lock);
   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::
 set_card_color(const LColor &card_color) {
+  MutexHolder holder(_lock);
   if (_card_color != card_color) {
     _card_color = card_color;
     invalidate_no_measure();
@@ -174,6 +187,7 @@ set_card_color(const LColor &card_color) {
  */
 INLINE LColor TextNode::
 get_card_color() const {
+  MutexHolder holder(_lock);
   return _card_color;
 }
 
@@ -185,7 +199,8 @@ set_card_texture(Texture *card_texture) {
   if (card_texture == nullptr) {
     clear_card_texture();
   } 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;
       _card_texture = card_texture;
       invalidate_no_measure();
@@ -198,7 +213,8 @@ set_card_texture(Texture *card_texture) {
  */
 INLINE void TextNode::
 clear_card_texture() {
-  if (has_card_texture()) {
+  MutexHolder holder(_lock);
+  if (_flags & F_has_card_texture) {
     _flags &= ~F_has_card_texture;
     _card_texture = nullptr;
     invalidate_no_measure();
@@ -210,6 +226,7 @@ clear_card_texture() {
  */
 INLINE bool TextNode::
 has_card_texture() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_card_texture) != 0;
 }
 
@@ -218,6 +235,7 @@ has_card_texture() const {
  */
 INLINE Texture *TextNode::
 get_card_texture() const {
+  MutexHolder holder(_lock);
   return _card_texture;
 }
 
@@ -229,6 +247,7 @@ get_card_texture() const {
  */
 INLINE void TextNode::
 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);
   _frame_ul.set(left, top);
   _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::
 set_frame_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= F_has_frame;
   _flags &= ~F_frame_as_margin;
   _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::
 clear_frame() {
+  MutexHolder holder(_lock);
   _flags &= ~F_has_frame;
   invalidate_no_measure();
 }
@@ -264,6 +285,7 @@ clear_frame() {
  */
 INLINE bool TextNode::
 has_frame() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_frame) != 0;
 }
 
@@ -276,7 +298,8 @@ has_frame() const {
  */
 INLINE bool TextNode::
 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;
 }
 
@@ -288,7 +311,8 @@ is_frame_as_margin() const {
  */
 INLINE LVecBase4 TextNode::
 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]);
 }
 
@@ -303,18 +327,20 @@ get_frame_as_set() const {
  */
 INLINE LVecBase4 TextNode::
 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();
     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::
 set_frame_line_width(PN_stdfloat frame_width) {
+  MutexHolder holder(_lock);
   _frame_width = frame_width;
   invalidate_no_measure();
 }
@@ -332,6 +359,7 @@ set_frame_line_width(PN_stdfloat frame_width) {
  */
 INLINE PN_stdfloat TextNode::
 get_frame_line_width() const {
+  MutexHolder holder(_lock);
   return _frame_width;
 }
 
@@ -342,6 +370,7 @@ get_frame_line_width() const {
  */
 INLINE void TextNode::
 set_frame_corners(bool corners) {
+  MutexHolder holder(_lock);
   if (corners) {
     _flags |= F_frame_corners;
   } else {
@@ -355,6 +384,7 @@ set_frame_corners(bool corners) {
  */
 INLINE bool TextNode::
 get_frame_corners() const {
+  MutexHolder holder(_lock);
   return (_flags & F_frame_corners) != 0;
 }
 
@@ -366,6 +396,7 @@ get_frame_corners() const {
  */
 INLINE void TextNode::
 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);
   _card_ul.set(left, top);
   _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::
 set_card_actual(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) {
+  MutexHolder holder(_lock);
   _flags |= F_has_card;
   _flags &= ~F_card_as_margin;
   _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::
 set_card_decal(bool card_decal) {
+  MutexHolder holder(_lock);
   if (card_decal) {
     _flags |= F_card_decal;
   } else {
@@ -407,6 +440,7 @@ set_card_decal(bool card_decal) {
  */
 INLINE void TextNode::
 clear_card() {
+  MutexHolder holder(_lock);
   _flags &= ~F_has_card;
   invalidate_no_measure();
 }
@@ -416,6 +450,7 @@ clear_card() {
  */
 INLINE bool TextNode::
 has_card() const {
+  MutexHolder holder(_lock);
   return (_flags & F_has_card) != 0;
 }
 
@@ -424,6 +459,7 @@ has_card() const {
  */
 INLINE bool TextNode::
 get_card_decal() const {
+  MutexHolder holder(_lock);
   return (_flags & F_card_decal) != 0;
 }
 
@@ -436,7 +472,8 @@ get_card_decal() const {
  */
 INLINE bool TextNode::
 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;
 }
 
@@ -448,7 +485,8 @@ is_card_as_margin() const {
  */
 INLINE LVecBase4 TextNode::
 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]);
 }
 
@@ -463,18 +501,20 @@ get_card_as_set() const {
  */
 INLINE LVecBase4 TextNode::
 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();
     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::
 get_card_transformed() const {
   LVecBase4 card = get_card_actual();
+
+  MutexHolder holder(_lock);
   LPoint3 ul = LPoint3(card[0], 0.0, card[3]) * _transform;
   LPoint3 lr = LPoint3(card[1], 0.0, card[2]) * _transform;
 
@@ -498,6 +540,7 @@ get_card_transformed() const {
  */
 INLINE void TextNode::
 set_transform(const LMatrix4 &transform) {
+  MutexHolder holder(_lock);
   _transform = transform;
   invalidate_with_measure();
 }
@@ -507,6 +550,7 @@ set_transform(const LMatrix4 &transform) {
  */
 INLINE LMatrix4 TextNode::
 get_transform() const {
+  MutexHolder holder(_lock);
   return _transform;
 }
 
@@ -515,6 +559,7 @@ get_transform() const {
  */
 INLINE void TextNode::
 set_coordinate_system(CoordinateSystem coordinate_system) {
+  MutexHolder holder(_lock);
   _coordinate_system = coordinate_system;
   invalidate_with_measure();
 }
@@ -524,6 +569,7 @@ set_coordinate_system(CoordinateSystem coordinate_system) {
  */
 INLINE CoordinateSystem TextNode::
 get_coordinate_system() const {
+  MutexHolder holder(_lock);
   return _coordinate_system;
 }
 
@@ -535,6 +581,7 @@ get_coordinate_system() const {
  */
 INLINE void TextNode::
 set_usage_hint(Geom::UsageHint usage_hint) {
+  MutexHolder holder(_lock);
   _usage_hint = usage_hint;
   invalidate_no_measure();
 }
@@ -545,6 +592,7 @@ set_usage_hint(Geom::UsageHint usage_hint) {
  */
 INLINE Geom::UsageHint TextNode::
 get_usage_hint() const {
+  MutexHolder holder(_lock);
   return _usage_hint;
 }
 
@@ -585,6 +633,7 @@ get_usage_hint() const {
  */
 INLINE void TextNode::
 set_flatten_flags(int flatten_flags) {
+  MutexHolder holder(_lock);
   _flatten_flags = flatten_flags;
 }
 
@@ -593,6 +642,7 @@ set_flatten_flags(int flatten_flags) {
  */
 INLINE int TextNode::
 get_flatten_flags() const {
+  MutexHolder holder(_lock);
   return _flatten_flags;
 }
 
@@ -602,6 +652,7 @@ get_flatten_flags() const {
  */
 INLINE void TextNode::
 set_font(TextFont *font) {
+  MutexHolder holder(_lock);
   TextProperties::set_font(font);
   invalidate_with_measure();
 }
@@ -611,6 +662,7 @@ set_font(TextFont *font) {
  */
 INLINE void TextNode::
 clear_font() {
+  MutexHolder holder(_lock);
   TextProperties::clear_font();
   invalidate_with_measure();
 }
@@ -631,6 +683,7 @@ clear_font() {
  */
 INLINE void TextNode::
 set_small_caps(bool small_caps) {
+  MutexHolder holder(_lock);
   TextProperties::set_small_caps(small_caps);
   invalidate_with_measure();
 }
@@ -640,6 +693,7 @@ set_small_caps(bool small_caps) {
  */
 INLINE void TextNode::
 clear_small_caps() {
+  MutexHolder holder(_lock);
   TextProperties::clear_small_caps();
   invalidate_with_measure();
 }
@@ -651,6 +705,7 @@ clear_small_caps() {
  */
 INLINE void TextNode::
 set_small_caps_scale(PN_stdfloat small_caps_scale) {
+  MutexHolder holder(_lock);
   TextProperties::set_small_caps_scale(small_caps_scale);
   invalidate_with_measure();
 }
@@ -660,6 +715,7 @@ set_small_caps_scale(PN_stdfloat small_caps_scale) {
  */
 INLINE void TextNode::
 clear_small_caps_scale() {
+  MutexHolder holder(_lock);
   TextProperties::clear_small_caps_scale();
   invalidate_with_measure();
 }
@@ -669,6 +725,7 @@ clear_small_caps_scale() {
  */
 INLINE void TextNode::
 set_slant(PN_stdfloat slant) {
+  MutexHolder holder(_lock);
   TextProperties::set_slant(slant);
   invalidate_with_measure();
 }
@@ -678,6 +735,7 @@ set_slant(PN_stdfloat slant) {
  */
 INLINE void TextNode::
 clear_slant() {
+  MutexHolder holder(_lock);
   TextProperties::clear_slant();
   invalidate_with_measure();
 }
@@ -687,6 +745,7 @@ clear_slant() {
  */
 INLINE void TextNode::
 set_align(TextNode::Alignment align_type) {
+  MutexHolder holder(_lock);
   TextProperties::set_align(align_type);
   invalidate_with_measure();
 }
@@ -696,6 +755,7 @@ set_align(TextNode::Alignment align_type) {
  */
 INLINE void TextNode::
 clear_align() {
+  MutexHolder holder(_lock);
   TextProperties::clear_align();
   invalidate_with_measure();
 }
@@ -706,6 +766,7 @@ clear_align() {
  */
 INLINE void TextNode::
 set_indent(PN_stdfloat indent) {
+  MutexHolder holder(_lock);
   TextProperties::set_indent(indent);
   invalidate_with_measure();
 }
@@ -715,6 +776,7 @@ set_indent(PN_stdfloat indent) {
  */
 INLINE void TextNode::
 clear_indent() {
+  MutexHolder holder(_lock);
   TextProperties::clear_indent();
   invalidate_with_measure();
 }
@@ -725,6 +787,7 @@ clear_indent() {
  */
 INLINE void TextNode::
 set_wordwrap(PN_stdfloat wordwrap) {
+  MutexHolder holder(_lock);
   TextProperties::set_wordwrap(wordwrap);
   invalidate_with_measure();
 }
@@ -735,6 +798,7 @@ set_wordwrap(PN_stdfloat wordwrap) {
  */
 INLINE void TextNode::
 clear_wordwrap() {
+  MutexHolder holder(_lock);
   TextProperties::clear_wordwrap();
   invalidate_with_measure();
 }
@@ -744,6 +808,7 @@ clear_wordwrap() {
  */
 INLINE void TextNode::
 set_text_color(const LColor &text_color) {
+  MutexHolder holder(_lock);
   TextProperties::set_text_color(text_color);
   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::
 clear_text_color() {
+  MutexHolder holder(_lock);
   TextProperties::clear_text_color();
   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::
 set_shadow_color(const LColor &shadow_color) {
+  MutexHolder holder(_lock);
   TextProperties::set_shadow_color(shadow_color);
   invalidate_no_measure();
 }
@@ -788,6 +855,7 @@ set_shadow_color(const LColor &shadow_color) {
  */
 INLINE void TextNode::
 clear_shadow_color() {
+  MutexHolder holder(_lock);
   TextProperties::clear_shadow_color();
   invalidate_with_measure();
 }
@@ -807,6 +875,7 @@ set_shadow(PN_stdfloat xoffset, PN_stdfloat yoffset) {
  */
 INLINE void TextNode::
 set_shadow(const LVecBase2 &shadow_offset) {
+  MutexHolder holder(_lock);
   TextProperties::set_shadow(shadow_offset);
   invalidate_no_measure();
 }
@@ -816,6 +885,7 @@ set_shadow(const LVecBase2 &shadow_offset) {
  */
 INLINE void TextNode::
 clear_shadow() {
+  MutexHolder holder(_lock);
   TextProperties::clear_shadow();
   invalidate_no_measure();
 }
@@ -831,6 +901,7 @@ clear_shadow() {
  */
 INLINE void TextNode::
 set_bin(const std::string &bin) {
+  MutexHolder holder(_lock);
   TextProperties::set_bin(bin);
   invalidate_no_measure();
 }
@@ -841,6 +912,7 @@ set_bin(const std::string &bin) {
  */
 INLINE void TextNode::
 clear_bin() {
+  MutexHolder holder(_lock);
   TextProperties::clear_bin();
   invalidate_no_measure();
 }
@@ -858,6 +930,7 @@ clear_bin() {
  */
 INLINE int TextNode::
 set_draw_order(int draw_order) {
+  MutexHolder holder(_lock);
   invalidate_no_measure();
   return TextProperties::set_draw_order(draw_order);
 }
@@ -867,6 +940,7 @@ set_draw_order(int draw_order) {
  */
 INLINE void TextNode::
 clear_draw_order() {
+  MutexHolder holder(_lock);
   TextProperties::clear_draw_order();
   invalidate_with_measure();
 }
@@ -877,6 +951,7 @@ clear_draw_order() {
  */
 INLINE void TextNode::
 set_tab_width(PN_stdfloat tab_width) {
+  MutexHolder holder(_lock);
   TextProperties::set_tab_width(tab_width);
   invalidate_with_measure();
 }
@@ -886,6 +961,7 @@ set_tab_width(PN_stdfloat tab_width) {
  */
 INLINE void TextNode::
 clear_tab_width() {
+  MutexHolder holder(_lock);
   TextProperties::clear_tab_width();
   invalidate_with_measure();
 }
@@ -897,6 +973,7 @@ clear_tab_width() {
  */
 INLINE void TextNode::
 set_glyph_scale(PN_stdfloat glyph_scale) {
+  MutexHolder holder(_lock);
   TextProperties::set_glyph_scale(glyph_scale);
   invalidate_with_measure();
 }
@@ -906,6 +983,7 @@ set_glyph_scale(PN_stdfloat glyph_scale) {
  */
 INLINE void TextNode::
 clear_glyph_scale() {
+  MutexHolder holder(_lock);
   TextProperties::clear_glyph_scale();
   invalidate_with_measure();
 }
@@ -917,6 +995,7 @@ clear_glyph_scale() {
  */
 INLINE void TextNode::
 set_glyph_shift(PN_stdfloat glyph_shift) {
+  MutexHolder holder(_lock);
   TextProperties::set_glyph_shift(glyph_shift);
   invalidate_with_measure();
 }
@@ -926,60 +1005,11 @@ set_glyph_shift(PN_stdfloat glyph_shift) {
  */
 INLINE void TextNode::
 clear_glyph_shift() {
+  MutexHolder holder(_lock);
   TextProperties::clear_glyph_shift();
   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
  * formatted by wordwrap rules.
@@ -1001,26 +1031,6 @@ calc_width(const std::string &line) const {
   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
  * formatted by wordwrap rules.
@@ -1030,6 +1040,7 @@ append_wtext(const std::wstring &wtext) {
  */
 INLINE std::wstring TextNode::
 get_wordwrapped_wtext() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _wordwrapped_wtext;
 }
@@ -1040,6 +1051,7 @@ get_wordwrapped_wtext() const {
  */
 INLINE PN_stdfloat TextNode::
 get_left() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_ul[0];
 }
@@ -1050,6 +1062,7 @@ get_left() const {
  */
 INLINE PN_stdfloat TextNode::
 get_right() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_lr[0];
 }
@@ -1060,6 +1073,7 @@ get_right() const {
  */
 INLINE PN_stdfloat TextNode::
 get_bottom() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_lr[1];
 }
@@ -1070,6 +1084,7 @@ get_bottom() const {
  */
 INLINE PN_stdfloat TextNode::
 get_top() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_ul[1];
 }
@@ -1079,6 +1094,7 @@ get_top() const {
  */
 INLINE PN_stdfloat TextNode::
 get_height() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_ul[1] - _text_lr[1];
 }
@@ -1088,6 +1104,7 @@ get_height() const {
  */
 INLINE PN_stdfloat TextNode::
 get_width() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_lr[0] - _text_ul[0];
 }
@@ -1098,6 +1115,7 @@ get_width() const {
  */
 INLINE LPoint3 TextNode::
 get_upper_left_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _ul3d;
 }
@@ -1108,6 +1126,7 @@ get_upper_left_3d() const {
  */
 INLINE LPoint3 TextNode::
 get_lower_right_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _lr3d;
 }
@@ -1118,10 +1137,22 @@ get_lower_right_3d() const {
  */
 INLINE int TextNode::
 get_num_rows() const {
+  MutexHolder holder(_lock);
   check_measure();
   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
  * node to recompute its text immediately, rather than waiting for it to be
@@ -1129,6 +1160,7 @@ get_num_rows() const {
  */
 INLINE void TextNode::
 update() {
+  MutexHolder holder(_lock);
   check_rebuild();
 }
 
@@ -1140,8 +1172,9 @@ update() {
  */
 INLINE void TextNode::
 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) {
-    set_small_caps(true);
+    TextProperties::set_small_caps(true);
   }
 
   _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
@@ -255,11 +255,16 @@ is_whitespace(wchar_t character) const {
  */
 PN_stdfloat TextNode::
 calc_width(const std::wstring &line) const {
+  TextFont *font = get_font();
+  if (font == nullptr) {
+    return 0.0f;
+  }
+
   PN_stdfloat width = 0.0f;
 
   std::wstring::const_iterator si;
   for (si = line.begin(); si != line.end(); ++si) {
-    width += calc_width(*si);
+    width += TextAssembler::calc_width(*si, *this);
   }
 
   return width;
@@ -272,10 +277,10 @@ void TextNode::
 output(std::ostream &out) const {
   PandaNode::output(out);
 
-  check_rebuild();
+  PT(PandaNode) internal_geom = do_get_internal_geom();
   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)";
@@ -286,6 +291,7 @@ output(std::ostream &out) const {
  */
 void TextNode::
 write(std::ostream &out, int indent_level) const {
+  MutexHolder holder(_lock);
   PandaNode::write(out, indent_level);
   TextProperties::write(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";
 }
 
-/**
- * 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
  * 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
  * is provided as a debugging aid only.
  */
-PandaNode *TextNode::
+PT(PandaNode) TextNode::
 get_internal_geom() const {
   // Output a nuisance warning to discourage the naive from calling this
   // method accidentally.
   text_cat.info()
     << "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::
 apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
                           GeomTransformer &transformer) {
+  MutexHolder holder(_lock);
   if ((attrib_types & SceneGraphReducer::TT_transform) != 0) {
     const LMatrix4 &mat = attribs._transform->get_mat();
     _transform *= mat;
@@ -520,10 +374,11 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
       const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color);
       if (ca->get_color_type() == ColorAttrib::T_flat) {
         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();
       if (s != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)) {
         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();
-        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,
                                  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;
@@ -617,10 +459,11 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
  */
 bool TextNode::
 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.
-    CullTraverserData next_data(data, _internal_geom);
+    CullTraverserData next_data(data, internal_geom);
     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
   // actually generating the text, if we have at least measured it.
-  check_measure();
-
   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);
 
@@ -681,9 +527,8 @@ compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
 void TextNode::
 r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
                 GeomTransformer &transformer, Thread *current_thread) {
-  check_rebuild();
 
-  PandaNode *child = _internal_geom;
+  PT(PandaNode) child = do_get_internal_geom();
   if (child != nullptr) {
     CPT(RenderState) child_state = node_state->compose(child->get_state());
     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::
 do_rebuild() {
+  nassertv(_lock.debug_is_locked());
   _flags &= ~(F_needs_rebuild | F_needs_measure);
-  _internal_geom = generate();
+  _internal_geom = do_generate();
 }
 
 
@@ -713,32 +559,216 @@ do_measure() {
   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.
  */
 PT(PandaNode) TextNode::
 make_frame() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   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(RenderState) state = RenderState::make(thick);
 
   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());
 
-  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_vertex(0);
   frame->close_primitive();
@@ -747,8 +777,8 @@ make_frame() {
   geom->add_primitive(frame);
   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);
     PT(Geom) geom2 = new Geom(vdata);
     geom2->add_primitive(corners);
@@ -763,30 +793,40 @@ make_frame() {
  */
 PT(PandaNode) TextNode::
 make_card() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   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
-    ("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 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->close_primitive();
 
@@ -805,13 +845,22 @@ make_card() {
  */
 PT(PandaNode) TextNode::
 make_card_with_border() {
+  nassertr(_lock.debug_is_locked(), nullptr);
+  nassertr((_flags & F_needs_measure) == 0, nullptr);
+
   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
@@ -820,57 +869,59 @@ make_card_with_border() {
  */
 
   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 texcoord(vdata, InternalName::get_texcoord());
 
   // 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);
   // 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);
-  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
-  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);
-  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
-  vertex.add_data3(right - _card_border_size, 0.02,
+  vertex.set_data3(right - _card_border_size, 0.02,
                     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
-  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
-  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
   card->add_consecutive_vertices(0, 8);

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

@@ -24,6 +24,8 @@
 #include "pandaNode.h"
 #include "luse.h"
 #include "geom.h"
+#include "pmutex.h"
+#include "mutexHolder.h"
 
 /**
  * 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 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
   // be wordwrapped.
   INLINE std::string get_wordwrapped_text() const;
@@ -201,10 +195,6 @@ PUBLISHED:
   bool has_character(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;
   PN_stdfloat calc_width(const std::wstring &line) const;
 
@@ -225,11 +215,11 @@ PUBLISHED:
 
   INLINE int get_num_rows() const;
 
-  PT(PandaNode) generate();
+  INLINE PT(PandaNode) generate();
   INLINE void update();
   INLINE void force_update();
 
-  PandaNode *get_internal_geom() const;
+  PT(PandaNode) get_internal_geom() const;
 
 PUBLISHED:
   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(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(small_caps, has_small_caps, get_small_caps,
                              set_small_caps, clear_small_caps);
@@ -279,6 +267,9 @@ PUBLISHED:
                              set_text_scale, clear_text_scale);
 
 public:
+  // From parent class TextEncoder;
+  virtual void text_changed() final;
+
   // From parent class PandaNode
   virtual int get_unsafe_to_apply_attribs() const;
   virtual void apply_attribs_to_vertices(const AccumulatedAttribs &attribs,
@@ -312,12 +303,16 @@ private:
   void do_rebuild();
   void do_measure();
 
+  PT(PandaNode) do_generate();
+  PT(PandaNode) do_get_internal_geom() const;
+
   PT(PandaNode) make_frame();
   PT(PandaNode) make_card();
   PT(PandaNode) make_card_with_border();
 
   static int count_geoms(PandaNode *node);
 
+  Mutex _lock;
   PT(PandaNode) _internal_geom;
 
   PT(Texture) _card_texture;

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

@@ -283,6 +283,25 @@ set_properties_now(WindowProperties &properties) {
     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()) {
     std::string title = properties.get_title();
     _properties.set_title(title);
@@ -487,7 +506,7 @@ open_window() {
   // CreateWindow() and know which window it is sending events to even before
   // it gives us a handle.  Warning: this is not thread safe!
   _creating_window = this;
-  bool opened = open_graphic_window(is_fullscreen());
+  bool opened = open_graphic_window();
   _creating_window = nullptr;
 
   if (!opened) {
@@ -865,7 +884,9 @@ do_fullscreen_switch() {
     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);
 
   WINDOW_METRICS metrics;
@@ -885,7 +906,10 @@ do_fullscreen_switch() {
 bool WinGraphicsWindow::
 do_windowed_switch() {
   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);
 
   WINDOW_METRICS metrics;
@@ -928,7 +952,7 @@ support_overlay_window(bool) {
  * Constructs a dwStyle for the specified mode, be it windowed or fullscreen.
  */
 DWORD WinGraphicsWindow::
-make_style(bool fullscreen) {
+make_style(const WindowProperties &properties) {
   // 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
   // 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;
 
-  if (fullscreen){
+  if (_properties.get_fullscreen()) {
     window_style |= WS_SYSMENU;
   } else if (!_properties.get_undecorated()) {
     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.
  */
 bool WinGraphicsWindow::
-open_graphic_window(bool fullscreen) {
-  DWORD window_style = make_style(fullscreen);
+open_graphic_window() {
+  DWORD window_style = make_style(_properties);
 
   wstring 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
     // effects.
     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,
-                           (PVOID)_saved_cursor_shadow, 0);
+                           _saved_cursor_shadow ? (PVOID)1 : nullptr, 0);
       SystemParametersInfo(SPI_SETMOUSEVANISH, 0,
-                           (PVOID)_saved_mouse_vanish, 0);
+                           _saved_mouse_vanish ? (PVOID)1 : nullptr, 0);
       _got_saved_params = false;
     }
 

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

@@ -119,7 +119,7 @@ protected:
   virtual bool calculate_metrics(bool fullscreen, DWORD style,
                                  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,
                                           DWORD &bitdepth);
@@ -127,7 +127,7 @@ protected:
   virtual void support_overlay_window(bool flag);
 
 private:
-  bool open_graphic_window(bool fullscreen);
+  bool open_graphic_window();
   void adjust_z_order();
   void adjust_z_order(WindowProperties::ZOrder last_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
         # 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,
         # 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.clear_render_textures()
 
-    depth_texture.write("test2.png")
-
     col = core.LColor()
     depth_texture.peek().lookup(col, 0.5, 0.5)
     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
 from panda3d import core
 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
@@ -52,7 +56,6 @@ def test_property2():
 
 
 # The next tests are for MAKE_SEQ_PROPERTY.
[email protected]
 def seq_property(*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():
     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():
@@ -411,7 +414,6 @@ def test_seq_property_extend():
 
 
 # The next tests are for MAKE_MAP_PROPERTY.
[email protected]
 def map_property(**items):
     """ Returns a mapping property initialized with the given values. """
 
@@ -425,11 +427,11 @@ def map_property(**items):
 
 def test_map_property_abc():
     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():
@@ -607,19 +609,19 @@ def test_map_property_update():
 def test_map_property_keys():
     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'))
 
 
 def test_map_property_values():
     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'))
 
 
 def test_map_property_items():
     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')))

+ 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))