Browse Source

Merge branch 'master' into input-overhaul

rdb 7 years ago
parent
commit
3ffe11485d
43 changed files with 783 additions and 431 deletions
  1. 0 3
      dtool/src/dtoolbase/typeHandle.cxx
  2. 0 3
      dtool/src/dtoolbase/typeHandle.h
  3. 0 31
      dtool/src/dtoolutil/dSearchPath.cxx
  4. 7 4
      dtool/src/dtoolutil/dSearchPath.h
  5. 3 0
      dtool/src/parser-inc/dirent.h
  6. 1 0
      dtool/src/parser-inc/sys/inotify.h
  7. 3 0
      dtool/src/parser-inc/sys/ioctl.h
  8. 3 0
      dtool/src/parser-inc/sys/mman.h
  9. 1 0
      dtool/src/parser-inc/sys/select.h
  10. 1 0
      dtool/src/parser-inc/sys/sysinfo.h
  11. 37 0
      dtool/src/prc/configDeclaration.cxx
  12. 3 0
      dtool/src/prc/configDeclaration.h
  13. 14 2
      dtool/src/prc/configVariableBool.cxx
  14. 1 9
      dtool/src/prc/configVariableFilename.cxx
  15. 25 5
      dtool/src/prc/configVariableSearchPath.I
  16. 3 10
      dtool/src/prc/configVariableSearchPath.cxx
  17. 4 3
      dtool/src/prc/configVariableSearchPath.h
  18. 2 2
      panda/src/chan/partBundle.h
  19. 1 1
      panda/src/egg/eggMesher.h
  20. 1 1
      panda/src/egg/eggMesherEdge.h
  21. 1 1
      panda/src/egg/eggMesherFanMaker.h
  22. 1 1
      panda/src/egg/eggMesherStrip.h
  23. 1 1
      panda/src/egg2pg/eggBinner.h
  24. 1 1
      panda/src/egg2pg/eggLoader.h
  25. 1 1
      panda/src/egg2pg/eggRenderState.h
  26. 1 1
      panda/src/egg2pg/eggSaver.h
  27. 10 0
      panda/src/grutil/shaderTerrainMesh.I
  28. 3 0
      panda/src/grutil/shaderTerrainMesh.cxx
  29. 3 0
      panda/src/grutil/shaderTerrainMesh.h
  30. 15 0
      panda/src/movies/movieTypeRegistry.cxx
  31. 3 0
      panda/src/movies/movieTypeRegistry.h
  32. 5 5
      panda/src/pgraph/colorAttrib.cxx
  33. 5 5
      panda/src/pgraph/colorScaleAttrib.cxx
  34. 1 1
      panda/src/pgraph/loader.cxx
  35. 16 7
      panda/src/pgraphnodes/shaderGenerator.cxx
  36. 2 0
      panda/src/pipeline/conditionVarSpinlockImpl.cxx
  37. 2 0
      panda/src/pipeline/reMutexSpinlockImpl.cxx
  38. 140 30
      panda/src/text/textNode.I
  39. 326 284
      panda/src/text/textNode.cxx
  40. 8 2
      panda/src/text/textNode.h
  41. 4 1
      samples/shader-terrain/main.py
  42. 18 16
      tests/interrogate/test_property.py
  43. 106 0
      tests/text/test_textnode.py

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

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

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

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

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

@@ -29,6 +29,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 +43,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 +56,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 +73,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 +95,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 +152,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 +166,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 +179,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 +196,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 +218,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 +273,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;
 };

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

@@ -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:

+ 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

@@ -207,7 +207,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

+ 140 - 30
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,6 +1005,7 @@ set_glyph_shift(PN_stdfloat glyph_shift) {
  */
 INLINE void TextNode::
 clear_glyph_shift() {
+  MutexHolder holder(_lock);
   TextProperties::clear_glyph_shift();
   invalidate_with_measure();
 }
@@ -936,6 +1016,7 @@ clear_glyph_shift() {
  */
 INLINE void TextNode::
 set_text(const std::string &text) {
+  MutexHolder holder(_lock);
   TextEncoder::set_text(text);
   invalidate_with_measure();
 }
@@ -948,6 +1029,7 @@ set_text(const std::string &text) {
  */
 INLINE void TextNode::
 set_text(const std::string &text, TextNode::Encoding encoding) {
+  MutexHolder holder(_lock);
   TextEncoder::set_text(text, encoding);
   invalidate_with_measure();
 }
@@ -957,6 +1039,7 @@ set_text(const std::string &text, TextNode::Encoding encoding) {
  */
 INLINE void TextNode::
 clear_text() {
+  MutexHolder holder(_lock);
   TextEncoder::clear_text();
   invalidate_with_measure();
 }
@@ -966,6 +1049,7 @@ clear_text() {
  */
 INLINE void TextNode::
 append_text(const std::string &text) {
+  MutexHolder holder(_lock);
   TextEncoder::append_text(text);
   invalidate_with_measure();
 }
@@ -976,6 +1060,7 @@ append_text(const std::string &text) {
  */
 INLINE void TextNode::
 append_unicode_char(wchar_t character) {
+  MutexHolder holder(_lock);
   TextEncoder::append_unicode_char(character);
   invalidate_with_measure();
 }
@@ -1008,6 +1093,7 @@ calc_width(const std::string &line) const {
  */
 INLINE void TextNode::
 set_wtext(const std::wstring &wtext) {
+  MutexHolder holder(_lock);
   TextEncoder::set_wtext(wtext);
   invalidate_with_measure();
 }
@@ -1017,6 +1103,7 @@ set_wtext(const std::wstring &wtext) {
  */
 INLINE void TextNode::
 append_wtext(const std::wstring &wtext) {
+  MutexHolder holder(_lock);
   TextEncoder::append_wtext(wtext);
   invalidate_with_measure();
 }
@@ -1030,6 +1117,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 +1128,7 @@ get_wordwrapped_wtext() const {
  */
 INLINE PN_stdfloat TextNode::
 get_left() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_ul[0];
 }
@@ -1050,6 +1139,7 @@ get_left() const {
  */
 INLINE PN_stdfloat TextNode::
 get_right() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_lr[0];
 }
@@ -1060,6 +1150,7 @@ get_right() const {
  */
 INLINE PN_stdfloat TextNode::
 get_bottom() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_lr[1];
 }
@@ -1070,6 +1161,7 @@ get_bottom() const {
  */
 INLINE PN_stdfloat TextNode::
 get_top() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _text_ul[1];
 }
@@ -1079,6 +1171,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 +1181,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 +1192,7 @@ get_width() const {
  */
 INLINE LPoint3 TextNode::
 get_upper_left_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _ul3d;
 }
@@ -1108,6 +1203,7 @@ get_upper_left_3d() const {
  */
 INLINE LPoint3 TextNode::
 get_lower_right_3d() const {
+  MutexHolder holder(_lock);
   check_measure();
   return _lr3d;
 }
@@ -1118,10 +1214,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 +1237,7 @@ get_num_rows() const {
  */
 INLINE void TextNode::
 update() {
+  MutexHolder holder(_lock);
   check_rebuild();
 }
 
@@ -1140,8 +1249,9 @@ update() {
  */
 INLINE void TextNode::
 force_update() {
-  invalidate_with_measure();
-  check_rebuild();
+  MutexHolder holder(_lock);
+  mark_internal_bounds_stale();
+  do_rebuild();
 }
 
 /**

+ 326 - 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,13 @@ 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();
 }
 
 /**
@@ -503,6 +347,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 +365,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 +379,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 +421,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 +450,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 +490,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 +518,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 +534,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 +550,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 +768,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 +784,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 +836,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 +860,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);

+ 8 - 2
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;
@@ -225,11 +227,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);
@@ -312,12 +314,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;

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

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