From ba9ea8ea2736a3b0731b30ef5d40a37833b0ce15 Mon Sep 17 00:00:00 2001 From: Younguk Kim Date: Wed, 19 Sep 2018 09:55:29 +0900 Subject: [PATCH 01/46] chan: add missing export macro --- panda/src/chan/partBundle.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/chan/partBundle.h b/panda/src/chan/partBundle.h index f8fda4bd47..7e9d21259d 100644 --- a/panda/src/chan/partBundle.h +++ b/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" From b64e850539a9df1f26f06c359e01db227089ffd6 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 19 Sep 2018 13:16:45 -0600 Subject: [PATCH 02/46] egg(2pg): Fix missing EXPCL_PANDA_EGG(2PG) --- panda/src/egg/eggMesher.h | 2 +- panda/src/egg/eggMesherEdge.h | 2 +- panda/src/egg/eggMesherFanMaker.h | 2 +- panda/src/egg/eggMesherStrip.h | 2 +- panda/src/egg2pg/eggBinner.h | 2 +- panda/src/egg2pg/eggLoader.h | 2 +- panda/src/egg2pg/eggRenderState.h | 2 +- panda/src/egg2pg/eggSaver.h | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/panda/src/egg/eggMesher.h b/panda/src/egg/eggMesher.h index 5d1a3e0070..5b4d9b63d1 100644 --- a/panda/src/egg/eggMesher.h +++ b/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(); diff --git a/panda/src/egg/eggMesherEdge.h b/panda/src/egg/eggMesherEdge.h index 5ef30c1c69..17e463b79d 100644 --- a/panda/src/egg/eggMesherEdge.h +++ b/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 ©); diff --git a/panda/src/egg/eggMesherFanMaker.h b/panda/src/egg/eggMesherFanMaker.h index f320b2858b..894462b493 100644 --- a/panda/src/egg/eggMesherFanMaker.h +++ b/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 Edges; typedef plist Strips; diff --git a/panda/src/egg/eggMesherStrip.h b/panda/src/egg/eggMesherStrip.h index 6064388ce0..72a2e54fa3 100644 --- a/panda/src/egg/eggMesherStrip.h +++ b/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, diff --git a/panda/src/egg2pg/eggBinner.h b/panda/src/egg2pg/eggBinner.h index a7e3c10894..007f434190 100644 --- a/panda/src/egg2pg/eggBinner.h +++ b/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 { diff --git a/panda/src/egg2pg/eggLoader.h b/panda/src/egg2pg/eggLoader.h index aa36475e05..eb81317bd8 100644 --- a/panda/src/egg2pg/eggLoader.h +++ b/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); diff --git a/panda/src/egg2pg/eggRenderState.h b/panda/src/egg2pg/eggRenderState.h index 44993dffcf..82af78be70 100644 --- a/panda/src/egg2pg/eggRenderState.h +++ b/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); diff --git a/panda/src/egg2pg/eggSaver.h b/panda/src/egg2pg/eggSaver.h index 8041d346cc..48a9693dec 100644 --- a/panda/src/egg2pg/eggSaver.h +++ b/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); From aacafe7be3cd8de64190535b3b454c5146bc69a7 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Sep 2018 13:03:47 +0200 Subject: [PATCH 03/46] dtoolutil: give DSearchPath a defaulted move constructor --- dtool/src/dtoolutil/dSearchPath.cxx | 31 ----------------------------- dtool/src/dtoolutil/dSearchPath.h | 11 ++++++---- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/dtool/src/dtoolutil/dSearchPath.cxx b/dtool/src/dtoolutil/dSearchPath.cxx index 4a094f1d98..c17b3d61ec 100644 --- a/dtool/src/dtoolutil/dSearchPath.cxx +++ b/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 ©) : - _directories(copy._directories) -{ -} - -/** - * - */ -void DSearchPath:: -operator = (const DSearchPath ©) { - _directories = copy._directories; -} - -/** - * - */ -DSearchPath:: -~DSearchPath() { -} - /** * Removes all the directories from the search list. */ diff --git a/dtool/src/dtoolutil/dSearchPath.h b/dtool/src/dtoolutil/dSearchPath.h index 8cb769c378..7258a71f26 100644 --- a/dtool/src/dtoolutil/dSearchPath.h +++ b/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 ©); - void operator = (const DSearchPath ©); - ~DSearchPath(); + DSearchPath(const DSearchPath ©) = default; + DSearchPath(DSearchPath &&from) = default; + ~DSearchPath() = default; + + DSearchPath &operator = (const DSearchPath ©) = default; + DSearchPath &operator = (DSearchPath &&from) = default; void clear(); void append_directory(const Filename &directory); From d6b7abedfe9d03c483f1973e34995c9201da46d0 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Sep 2018 13:05:52 +0200 Subject: [PATCH 04/46] prc: fix some race conditions querying bool and searchpath vars This is not perfect, and we need to more thoroughly address thread safety in the PRC system, but it will nonetheless address a lot of the race condition issues when querying these variables from two threads at the same time. --- dtool/src/prc/configDeclaration.cxx | 37 ++++++++++++++++++++++ dtool/src/prc/configDeclaration.h | 3 ++ dtool/src/prc/configVariableBool.cxx | 16 ++++++++-- dtool/src/prc/configVariableFilename.cxx | 10 +----- dtool/src/prc/configVariableSearchPath.I | 22 +++++++++++-- dtool/src/prc/configVariableSearchPath.cxx | 13 ++------ dtool/src/prc/configVariableSearchPath.h | 5 +-- 7 files changed, 80 insertions(+), 26 deletions(-) diff --git a/dtool/src/prc/configDeclaration.cxx b/dtool/src/prc/configDeclaration.cxx index 7cbfa5ba6e..b401df73d4 100644 --- a/dtool/src/prc/configDeclaration.cxx +++ b/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; +} + /** * */ diff --git a/dtool/src/prc/configDeclaration.h b/dtool/src/prc/configDeclaration.h index 38129670d5..11cb97728a 100644 --- a/dtool/src/prc/configDeclaration.h +++ b/dtool/src/prc/configDeclaration.h @@ -19,6 +19,7 @@ #include "configPage.h" #include "vector_string.h" #include "numeric_types.h" +#include "filename.h" #include @@ -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; diff --git a/dtool/src/prc/configVariableBool.cxx b/dtool/src/prc/configVariableBool.cxx index 7fb604642d..701f40c65d 100644 --- a/dtool/src/prc/configVariableBool.cxx +++ b/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(); } diff --git a/dtool/src/prc/configVariableFilename.cxx b/dtool/src/prc/configVariableFilename.cxx index 4c8941babd..1015a32e66 100644 --- a/dtool/src/prc/configVariableFilename.cxx +++ b/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(); diff --git a/dtool/src/prc/configVariableSearchPath.I b/dtool/src/prc/configVariableSearchPath.I index 46e542e24c..89a9ac7df9 100644 --- a/dtool/src/prc/configVariableSearchPath.I +++ b/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(); } /** diff --git a/dtool/src/prc/configVariableSearchPath.cxx b/dtool/src/prc/configVariableSearchPath.cxx index 2231626a7a..32620253b3 100644 --- a/dtool/src/prc/configVariableSearchPath.cxx +++ b/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)); } } diff --git a/dtool/src/prc/configVariableSearchPath.h b/dtool/src/prc/configVariableSearchPath.h index 02a4fd65c3..12ad1c54c9 100644 --- a/dtool/src/prc/configVariableSearchPath.h +++ b/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); @@ -81,6 +81,7 @@ PUBLISHED: private: void reload_search_path(); + mutable MutexImpl _lock; DSearchPath _default_value; DSearchPath _prefix, _postfix; From 77724f49dc4036fe5b3151a5fd2f44d354fc1ae1 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Sep 2018 13:50:06 +0200 Subject: [PATCH 05/46] dtoolbase: remove TypeHandle::_none symbol, no longer needed --- dtool/src/dtoolbase/typeHandle.cxx | 3 --- dtool/src/dtoolbase/typeHandle.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/dtool/src/dtoolbase/typeHandle.cxx b/dtool/src/dtoolbase/typeHandle.cxx index 1b96352723..15fac6b60f 100644 --- a/dtool/src/dtoolbase/typeHandle.cxx +++ b/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 diff --git a/dtool/src/dtoolbase/typeHandle.h b/dtool/src/dtoolbase/typeHandle.h index da66074750..97dc445443 100644 --- a/dtool/src/dtoolbase/typeHandle.h +++ b/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; }; From a3a7c0cf9da82be9ab59e164f7a05b5715eb333b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Sep 2018 14:21:19 +0200 Subject: [PATCH 06/46] parser-inc: add more POSIX system header stubs --- dtool/src/parser-inc/dirent.h | 3 +++ dtool/src/parser-inc/sys/inotify.h | 1 + dtool/src/parser-inc/sys/ioctl.h | 3 +++ dtool/src/parser-inc/sys/mman.h | 3 +++ dtool/src/parser-inc/sys/select.h | 1 + dtool/src/parser-inc/sys/sysinfo.h | 1 + 6 files changed, 12 insertions(+) create mode 100644 dtool/src/parser-inc/dirent.h create mode 100644 dtool/src/parser-inc/sys/inotify.h create mode 100644 dtool/src/parser-inc/sys/ioctl.h create mode 100644 dtool/src/parser-inc/sys/mman.h create mode 100644 dtool/src/parser-inc/sys/select.h create mode 100644 dtool/src/parser-inc/sys/sysinfo.h diff --git a/dtool/src/parser-inc/dirent.h b/dtool/src/parser-inc/dirent.h new file mode 100644 index 0000000000..5c0877bfcd --- /dev/null +++ b/dtool/src/parser-inc/dirent.h @@ -0,0 +1,3 @@ +typedef struct __dirstream DIR; +struct dirent; +typedef unsigned long ino_t; diff --git a/dtool/src/parser-inc/sys/inotify.h b/dtool/src/parser-inc/sys/inotify.h new file mode 100644 index 0000000000..52ef884e79 --- /dev/null +++ b/dtool/src/parser-inc/sys/inotify.h @@ -0,0 +1 @@ +struct inotify_event; diff --git a/dtool/src/parser-inc/sys/ioctl.h b/dtool/src/parser-inc/sys/ioctl.h new file mode 100644 index 0000000000..d57d62d983 --- /dev/null +++ b/dtool/src/parser-inc/sys/ioctl.h @@ -0,0 +1,3 @@ +struct winsize; +struct termio; + diff --git a/dtool/src/parser-inc/sys/mman.h b/dtool/src/parser-inc/sys/mman.h new file mode 100644 index 0000000000..c540c66b78 --- /dev/null +++ b/dtool/src/parser-inc/sys/mman.h @@ -0,0 +1,3 @@ +#include + +struct posix_typed_mem_info; diff --git a/dtool/src/parser-inc/sys/select.h b/dtool/src/parser-inc/sys/select.h new file mode 100644 index 0000000000..18a03a58e7 --- /dev/null +++ b/dtool/src/parser-inc/sys/select.h @@ -0,0 +1 @@ +#include diff --git a/dtool/src/parser-inc/sys/sysinfo.h b/dtool/src/parser-inc/sys/sysinfo.h new file mode 100644 index 0000000000..72e2bef1b2 --- /dev/null +++ b/dtool/src/parser-inc/sys/sysinfo.h @@ -0,0 +1 @@ +struct sysinfo; From 3ac50a23473ce251019da9fe3a880763cf9471bf Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Sep 2018 14:22:41 +0200 Subject: [PATCH 07/46] movies: fix crash on simultaneous threaded audio/video load --- panda/src/movies/movieTypeRegistry.cxx | 15 +++++++++++++++ panda/src/movies/movieTypeRegistry.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/panda/src/movies/movieTypeRegistry.cxx b/panda/src/movies/movieTypeRegistry.cxx index 012deff1cd..51468ac2a4 100644 --- a/panda/src/movies/movieTypeRegistry.cxx +++ b/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; diff --git a/panda/src/movies/movieTypeRegistry.h b/panda/src/movies/movieTypeRegistry.h index ee5fc14da2..c9ddb53f15 100644 --- a/panda/src/movies/movieTypeRegistry.h +++ b/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 _audio_type_registry; pmap _deferred_audio_types; + ReMutex _video_lock; pmap _video_type_registry; pmap _deferred_video_types; }; From 5457d76b947273bf0eae561b2de3e52fc1df08a4 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 11:08:22 +0200 Subject: [PATCH 08/46] text: slight perf improvement for TextNode card/frame generation --- panda/src/text/textNode.cxx | 109 +++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index 92001d7b40..a68ebba812 100644 --- a/panda/src/text/textNode.cxx +++ b/panda/src/text/textNode.cxx @@ -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; @@ -730,15 +735,16 @@ make_frame() { 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(); @@ -772,19 +778,20 @@ make_card() { PN_stdfloat top = dimensions[3]; 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()); card->add_consecutive_vertices(0, 4); @@ -820,57 +827,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); + 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.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, + 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); From 1e084e0b2b16046ed4d996b086880497d64d3ad2 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 11:35:16 +0200 Subject: [PATCH 09/46] text: add thread safety to TextNode This does not 100% cover all the base class TextProperties, however, so you still need to be careful not to access those from two threads at once. --- panda/src/text/textNode.I | 170 +++++++++-- panda/src/text/textNode.cxx | 595 +++++++++++++++++++----------------- panda/src/text/textNode.h | 10 +- 3 files changed, 462 insertions(+), 313 deletions(-) diff --git a/panda/src/text/textNode.I b/panda/src/text/textNode.I index 313685c504..8070504c6b 100644 --- a/panda/src/text/textNode.I +++ b/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(); } /** diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index a68ebba812..1ecfc1ea65 100644 --- a/panda/src/text/textNode.cxx +++ b/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); @@ -277,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)"; @@ -291,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) @@ -301,13 +302,263 @@ write(std::ostream &out, int indent_level) const { << "text is " << get_text() << "\n"; } +/** + * Returns the actual node that is used internally to render the text, if the + * TextNode is parented within the scene graph. + * + * In general, you should not call this method. Call generate() instead if + * you want to get a handle to geometry that represents the text. This method + * is provided as a debugging aid only. + */ +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"; + return do_get_internal_geom(); +} + +/** + * Returns the union of all attributes from SceneGraphReducer::AttribTypes + * that may not safely be applied to the vertices of this node. If this is + * nonzero, these attributes must be dropped at this node as a state change. + * + * This is a generalization of safe_to_transform(). + */ +int TextNode:: +get_unsafe_to_apply_attribs() const { + // We have no way to apply these kinds of attributes to our TextNode, so + // insist they get dropped into the PandaNode's basic state. + return + SceneGraphReducer::TT_tex_matrix | + SceneGraphReducer::TT_other; +} + +/** + * Applies whatever attributes are specified in the AccumulatedAttribs object + * (and by the attrib_types bitmask) to the vertices on this node, if + * appropriate. If this node uses geom arrays like a GeomNode, the supplied + * GeomTransformer may be used to unify shared arrays across multiple + * different nodes. + * + * This is a generalization of xform(). + */ +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; + + if ((_flags & F_needs_measure) == 0) { + // If we already have a measure, transform it too. We don't need to + // invalidate the 2-d parts, since that's not affected by the transform + // anyway. + _ul3d = _ul3d * mat; + _lr3d = _lr3d * mat; + } + } + if ((attrib_types & SceneGraphReducer::TT_color) != 0) { + if (attribs._color != nullptr) { + const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color); + if (ca->get_color_type() == ColorAttrib::T_flat) { + const LColor &c = ca->get_color(); + TextProperties::set_text_color(c); + TextProperties::set_shadow_color(c); + _frame_color = c; + _card_color = c; + invalidate_no_measure(); + } + } + } + if ((attrib_types & SceneGraphReducer::TT_color_scale) != 0) { + if (attribs._color_scale != nullptr) { + const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, attribs._color_scale); + const LVecBase4 &s = csa->get_scale(); + if (s != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)) { + LVecBase4 tc = get_text_color(); + tc.componentwise_mult(s); + TextProperties::set_text_color(tc); + + LVecBase4 sc = get_shadow_color(); + sc.componentwise_mult(s); + TextProperties::set_shadow_color(sc); + + _frame_color.componentwise_mult(s); + _card_color.componentwise_mult(s); + + invalidate_no_measure(); + } + } + } + + // Now propagate the attributes down to our already-generated geometry, if + // we have any. + if ((_flags & F_needs_rebuild) == 0 && + _internal_geom != nullptr) { + SceneGraphReducer gr; + gr.apply_attribs(_internal_geom, attribs, attrib_types, transformer); + } +} + +/** + * This is used to support NodePath::calc_tight_bounds(). It is not intended + * to be called directly, and it has nothing to do with the normal Panda + * bounding-volume computation. + * + * If the node contains any geometry, this updates min_point and max_point to + * enclose its bounding box. found_any is to be set true if the node has any + * geometry at all, or left alone if it has none. This method may be called + * over several nodes, so it may enter with min_point, max_point, and + * found_any already set. + */ +CPT(TransformState) TextNode:: +calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any, + const TransformState *transform, Thread *current_thread) const { + CPT(TransformState) next_transform = + PandaNode::calc_tight_bounds(min_point, max_point, found_any, 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; +} + +/** + * This function will be called during the cull traversal to perform any + * additional operations that should be performed at cull time. This may + * include additional manipulation of render state or additional + * visible/invisible decisions, or any other arbitrary operation. + * + * Note that this function will *not* be called unless set_cull_callback() is + * called in the constructor of the derived class. It is necessary to call + * set_cull_callback() to indicated that we require cull_callback() to be + * called. + * + * By the time this function is called, the node has already passed the + * bounding-volume test for the viewing frustum, and the node's transform and + * state have already been applied to the indicated CullTraverserData object. + * + * The return value is true if this node should be visible, or false if it + * should be culled. + */ +bool TextNode:: +cull_callback(CullTraverser *trav, CullTraverserData &data) { + + PT(PandaNode) internal_geom = do_get_internal_geom(); + if (internal_geom != nullptr) { + // Render the text with this node. + CullTraverserData next_data(data, internal_geom); + trav->traverse(next_data); + } + + // Now continue to render everything else below this node. + return true; +} + +/** + * Returns true if there is some value to visiting this particular node during + * the cull traversal for any camera, false otherwise. This will be used to + * optimize the result of get_net_draw_show_mask(), so that any subtrees that + * contain only nodes for which is_renderable() is false need not be visited. + */ +bool TextNode:: +is_renderable() const { + return true; +} + +/** + * Called when needed to recompute the node's _internal_bound object. Nodes + * that contain anything of substance should redefine this to do the right + * thing. + */ +void TextNode:: +compute_internal_bounds(CPT(BoundingVolume) &internal_bounds, + int &internal_vertices, + int pipeline_stage, + Thread *current_thread) const { + // First, get ourselves a fresh, empty bounding volume. + PT(BoundingVolume) bound = new BoundingSphere; + + GeometricBoundingVolume *gbv = DCAST(GeometricBoundingVolume, bound); + + // Now enclose the bounding box around the text. We can do this without + // actually generating the text, if we have at least measured it. + LPoint3 vertices[8]; + { + 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); + + internal_bounds = bound; + internal_vertices = 0; // TODO: estimate this better. +} + +/** + * The recursive implementation of prepare_scene(). Don't call this directly; + * call PandaNode::prepare_scene() or NodePath::prepare_scene() instead. + */ +void TextNode:: +r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state, + GeomTransformer &transformer, Thread *current_thread) { + + 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); + } + + PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread); +} + +/** + * Removes any existing children of the TextNode, and adds the newly generated + * text instead. + */ +void TextNode:: +do_rebuild() { + nassertv(_lock.debug_is_locked()); + _flags &= ~(F_needs_rebuild | F_needs_measure); + _internal_geom = do_generate(); +} + + +/** + * Can be called in lieu of do_rebuild() to measure the text and set up the + * bounding boxes properly without actually assembling it. + */ +void TextNode:: +do_measure() { + // We no longer make this a special case. + 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:: -generate() { +do_generate() { + nassertr(_lock.debug_is_locked(), nullptr); + PStatTimer timer(_text_generate_pcollector); if (text_cat.is_debug()) { text_cat.debug() @@ -408,20 +659,20 @@ generate() { // Now deal with the decorations. - if (has_card()) { + if (_flags & F_has_card) { PT(PandaNode) card_root; - if (has_card_border()) { + 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(get_card_color())); - if (get_card_color()[3] != 1.0f) { + 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 (has_card_texture()) { - card_root->set_attrib(TextureAttrib::make(get_card_texture())); + if (_flags & F_has_card_texture) { + card_root->set_attrib(TextureAttrib::make(_card_texture)); } if (has_bin()) { @@ -437,17 +688,17 @@ generate() { card_root->add_child(root); root = card_root; - if (get_card_decal()) { + if (_flags & F_card_decal) { card_root->set_effect(DecalEffect::make()); } } - if (has_frame()) { + 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(get_frame_color())); - if (get_frame_color()[3] != 1.0f) { + frame_root->set_attrib(ColorAttrib::make_flat(_frame_color)); + if (_frame_color[3] != 1.0f) { frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); } @@ -465,271 +716,35 @@ generate() { /** * Returns the actual node that is used internally to render the text, if the * TextNode is parented within the scene graph. - * - * In general, you should not call this method. Call generate() instead if - * you want to get a handle to geometry that represents the text. This method - * is provided as a debugging aid only. */ -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"; +PT(PandaNode) TextNode:: +do_get_internal_geom() const { + MutexHolder holder(_lock); check_rebuild(); return _internal_geom; } -/** - * Returns the union of all attributes from SceneGraphReducer::AttribTypes - * that may not safely be applied to the vertices of this node. If this is - * nonzero, these attributes must be dropped at this node as a state change. - * - * This is a generalization of safe_to_transform(). - */ -int TextNode:: -get_unsafe_to_apply_attribs() const { - // We have no way to apply these kinds of attributes to our TextNode, so - // insist they get dropped into the PandaNode's basic state. - return - SceneGraphReducer::TT_tex_matrix | - SceneGraphReducer::TT_other; -} - -/** - * Applies whatever attributes are specified in the AccumulatedAttribs object - * (and by the attrib_types bitmask) to the vertices on this node, if - * appropriate. If this node uses geom arrays like a GeomNode, the supplied - * GeomTransformer may be used to unify shared arrays across multiple - * different nodes. - * - * This is a generalization of xform(). - */ -void TextNode:: -apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types, - GeomTransformer &transformer) { - if ((attrib_types & SceneGraphReducer::TT_transform) != 0) { - const LMatrix4 &mat = attribs._transform->get_mat(); - _transform *= mat; - - if ((_flags & F_needs_measure) == 0) { - // If we already have a measure, transform it too. We don't need to - // invalidate the 2-d parts, since that's not affected by the transform - // anyway. - _ul3d = _ul3d * mat; - _lr3d = _lr3d * mat; - } - } - if ((attrib_types & SceneGraphReducer::TT_color) != 0) { - if (attribs._color != nullptr) { - 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); - } - } - } - if ((attrib_types & SceneGraphReducer::TT_color_scale) != 0) { - if (attribs._color_scale != nullptr) { - const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, attribs._color_scale); - 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); - 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); - } - } - } - - // Now propagate the attributes down to our already-generated geometry, if - // we have any. - if ((_flags & F_needs_rebuild) == 0 && - _internal_geom != nullptr) { - SceneGraphReducer gr; - gr.apply_attribs(_internal_geom, attribs, attrib_types, transformer); - } -} - -/** - * This is used to support NodePath::calc_tight_bounds(). It is not intended - * to be called directly, and it has nothing to do with the normal Panda - * bounding-volume computation. - * - * If the node contains any geometry, this updates min_point and max_point to - * enclose its bounding box. found_any is to be set true if the node has any - * geometry at all, or left alone if it has none. This method may be called - * over several nodes, so it may enter with min_point, max_point, and - * found_any already set. - */ -CPT(TransformState) TextNode:: -calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any, - const TransformState *transform, Thread *current_thread) const { - CPT(TransformState) next_transform = - 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); - } - - return next_transform; -} - -/** - * This function will be called during the cull traversal to perform any - * additional operations that should be performed at cull time. This may - * include additional manipulation of render state or additional - * visible/invisible decisions, or any other arbitrary operation. - * - * Note that this function will *not* be called unless set_cull_callback() is - * called in the constructor of the derived class. It is necessary to call - * set_cull_callback() to indicated that we require cull_callback() to be - * called. - * - * By the time this function is called, the node has already passed the - * bounding-volume test for the viewing frustum, and the node's transform and - * state have already been applied to the indicated CullTraverserData object. - * - * The return value is true if this node should be visible, or false if it - * should be culled. - */ -bool TextNode:: -cull_callback(CullTraverser *trav, CullTraverserData &data) { - check_rebuild(); - if (_internal_geom != nullptr) { - // Render the text with this node. - CullTraverserData next_data(data, _internal_geom); - trav->traverse(next_data); - } - - // Now continue to render everything else below this node. - return true; -} - -/** - * Returns true if there is some value to visiting this particular node during - * the cull traversal for any camera, false otherwise. This will be used to - * optimize the result of get_net_draw_show_mask(), so that any subtrees that - * contain only nodes for which is_renderable() is false need not be visited. - */ -bool TextNode:: -is_renderable() const { - return true; -} - -/** - * Called when needed to recompute the node's _internal_bound object. Nodes - * that contain anything of substance should redefine this to do the right - * thing. - */ -void TextNode:: -compute_internal_bounds(CPT(BoundingVolume) &internal_bounds, - int &internal_vertices, - int pipeline_stage, - Thread *current_thread) const { - // First, get ourselves a fresh, empty bounding volume. - PT(BoundingVolume) bound = new BoundingSphere; - - GeometricBoundingVolume *gbv = DCAST(GeometricBoundingVolume, bound); - - // 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]); - - gbv->around(vertices, vertices + 8); - - internal_bounds = bound; - internal_vertices = 0; // TODO: estimate this better. -} - -/** - * The recursive implementation of prepare_scene(). Don't call this directly; - * call PandaNode::prepare_scene() or NodePath::prepare_scene() instead. - */ -void TextNode:: -r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state, - GeomTransformer &transformer, Thread *current_thread) { - check_rebuild(); - - PandaNode *child = _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); - } - - PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread); -} - -/** - * Removes any existing children of the TextNode, and adds the newly generated - * text instead. - */ -void TextNode:: -do_rebuild() { - _flags &= ~(F_needs_rebuild | F_needs_measure); - _internal_geom = generate(); -} - - -/** - * Can be called in lieu of do_rebuild() to measure the text and set up the - * bounding boxes properly without actually assembling it. - */ -void TextNode:: -do_measure() { - // We no longer make this a special case. - do_rebuild(); -} - /** * 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); @@ -753,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); @@ -769,13 +784,22 @@ 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(), _usage_hint); @@ -793,7 +817,7 @@ make_card() { 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(); @@ -812,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 diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h index 4f37f806f4..ecf7dcafcf 100644 --- a/panda/src/text/textNode.h +++ b/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; From a6ad608207c42de37017313e0eb0fddde5cd21be Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 11:38:03 +0200 Subject: [PATCH 10/46] tests: add some unit tests for TextNode --- tests/text/test_textnode.py | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/text/test_textnode.py diff --git a/tests/text/test_textnode.py b/tests/text/test_textnode.py new file mode 100644 index 0000000000..1f9a8349b9 --- /dev/null +++ b/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 == color + assert text.shadow_color == color + assert text.frame_color == color + assert text.card_color == 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 == (.5, 0, 0, 0) + assert text.shadow_color == (0, .5, 0, 0) + assert text.frame_color == (0, 0, .5, 0) + assert text.card_color == (0, 0, 0, .5) From cd033c27e8f458d3c8e0fb19380eb2906a232435 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 21:00:08 +0200 Subject: [PATCH 11/46] grutil: add thread safety to ShaderTerrainMesh --- panda/src/grutil/shaderTerrainMesh.I | 10 ++++++++++ panda/src/grutil/shaderTerrainMesh.cxx | 3 +++ panda/src/grutil/shaderTerrainMesh.h | 3 +++ 3 files changed, 16 insertions(+) diff --git a/panda/src/grutil/shaderTerrainMesh.I b/panda/src/grutil/shaderTerrainMesh.I index f6a66bc7f5..f4a6b4286e 100644 --- a/panda/src/grutil/shaderTerrainMesh.I +++ b/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; } diff --git a/panda/src/grutil/shaderTerrainMesh.cxx b/panda/src/grutil/shaderTerrainMesh.cxx index 44c8397279..a0f3c34690 100644 --- a/panda/src/grutil/shaderTerrainMesh.cxx +++ b/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 diff --git a/panda/src/grutil/shaderTerrainMesh.h b/panda/src/grutil/shaderTerrainMesh.h index c57a5d4e74..9c4f8d9a3a 100644 --- a/panda/src/grutil/shaderTerrainMesh.h +++ b/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 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; From 4c67861a289e3e0816cb1ee166d9761a4d4ebc84 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 21:03:09 +0200 Subject: [PATCH 12/46] samples: set heightfield to clamp mode in shader-terrain sample --- samples/shader-terrain/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/shader-terrain/main.py b/samples/shader-terrain/main.py index 92ff61467a..10bffb3767 100644 --- a/samples/shader-terrain/main.py +++ b/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. From a099c852459be8a59ef1d8044c09ceea869cb3ed Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 25 Sep 2018 21:18:48 +0200 Subject: [PATCH 13/46] ShaderGenerator: fix broken handling of CO_undefined alpha operand Fixes #394 --- panda/src/pgraphnodes/shaderGenerator.cxx | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index d954ddcafc..1b08e921b0 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/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()); From 5ae38a8a924d839cba7a5d0a99112909c533787c Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 27 Sep 2018 22:14:47 +0200 Subject: [PATCH 14/46] Fix crash loading from search path Possible fix for #395 --- dtool/src/prc/configVariableSearchPath.I | 8 ++++++-- dtool/src/prc/configVariableSearchPath.h | 2 +- panda/src/pgraph/loader.cxx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dtool/src/prc/configVariableSearchPath.I b/dtool/src/prc/configVariableSearchPath.I index 89a9ac7df9..37c4383c02 100644 --- a/dtool/src/prc/configVariableSearchPath.I +++ b/dtool/src/prc/configVariableSearchPath.I @@ -229,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; } /** diff --git a/dtool/src/prc/configVariableSearchPath.h b/dtool/src/prc/configVariableSearchPath.h index 12ad1c54c9..ca575c078a 100644 --- a/dtool/src/prc/configVariableSearchPath.h +++ b/dtool/src/prc/configVariableSearchPath.h @@ -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); diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index 03ae0e0587..3c04578a55 100644 --- a/panda/src/pgraph/loader.cxx +++ b/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); From c43d9b50029c59db212be10c0d31f89653f7b6db Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 27 Sep 2018 22:23:10 +0200 Subject: [PATCH 15/46] tests: fix issue with double-precision TextNode tests --- tests/text/test_textnode.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/text/test_textnode.py b/tests/text/test_textnode.py index 1f9a8349b9..e96c9e5371 100644 --- a/tests/text/test_textnode.py +++ b/tests/text/test_textnode.py @@ -82,10 +82,10 @@ def test_textnode_flatten_color(): path.set_color(color) path.flatten_strong() - assert text.text_color == color - assert text.shadow_color == color - assert text.frame_color == color - assert text.card_color == color + 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(): @@ -100,7 +100,7 @@ def test_textnode_flatten_colorscale(): path.set_color_scale(color) path.flatten_strong() - assert text.text_color == (.5, 0, 0, 0) - assert text.shadow_color == (0, .5, 0, 0) - assert text.frame_color == (0, 0, .5, 0) - assert text.card_color == (0, 0, 0, .5) + 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)) From fac82e6dcaeda1cb205ecdb1f7fcd0a04c883893 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 13:19:36 +0200 Subject: [PATCH 16/46] pgraph: fix precision issues with Color(Scale)Attrib quantization --- panda/src/pgraph/colorAttrib.cxx | 10 +++++----- panda/src/pgraph/colorScaleAttrib.cxx | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/panda/src/pgraph/colorAttrib.cxx b/panda/src/pgraph/colorAttrib.cxx index 7bfbecee79..129f5a9184 100644 --- a/panda/src/pgraph/colorAttrib.cxx +++ b/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: diff --git a/panda/src/pgraph/colorScaleAttrib.cxx b/panda/src/pgraph/colorScaleAttrib.cxx index 088c3142ef..04001dd0ab 100644 --- a/panda/src/pgraph/colorScaleAttrib.cxx +++ b/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; } /** From cd9673ae9b10123e9376a52fd29bb93e2d39092f Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 14:19:40 +0200 Subject: [PATCH 17/46] tests: fix pytest deprecation warnings --- tests/interrogate/test_property.py | 34 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/interrogate/test_property.py b/tests/interrogate/test_property.py index e39f45f9dc..4d521d03e5 100755 --- a/tests/interrogate/test_property.py +++ b/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. -@pytest.fixture 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. -@pytest.fixture 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'))) From 183d66a5c2620034854a79e2e2e3b6f78f7e59f9 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 14:20:47 +0200 Subject: [PATCH 18/46] pipeline: fix compiler warning due to PAUSE symbol redefined --- panda/src/pipeline/conditionVarSpinlockImpl.cxx | 2 ++ panda/src/pipeline/reMutexSpinlockImpl.cxx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/panda/src/pipeline/conditionVarSpinlockImpl.cxx b/panda/src/pipeline/conditionVarSpinlockImpl.cxx index d35d8cd1f7..2b4be0d4e7 100644 --- a/panda/src/pipeline/conditionVarSpinlockImpl.cxx +++ b/panda/src/pipeline/conditionVarSpinlockImpl.cxx @@ -58,4 +58,6 @@ wait(double timeout) { _mutex.lock(); } +#undef PAUSE + #endif // MUTEX_SPINLOCK diff --git a/panda/src/pipeline/reMutexSpinlockImpl.cxx b/panda/src/pipeline/reMutexSpinlockImpl.cxx index 0de12f986f..de0b2c0355 100644 --- a/panda/src/pipeline/reMutexSpinlockImpl.cxx +++ b/panda/src/pipeline/reMutexSpinlockImpl.cxx @@ -54,4 +54,6 @@ try_lock() { } } +#undef PAUSE + #endif // MUTEX_SPINLOCK From 87d1048f7913834a2e78dc5b45182007d00e2253 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 30 Sep 2018 15:43:32 -0600 Subject: [PATCH 19/46] mathutil: mersenne.h parameters should not be an enum This makes no sense as an enum; they're constants, so they should be static const. --- panda/src/mathutil/mersenne.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/panda/src/mathutil/mersenne.h b/panda/src/mathutil/mersenne.h index 7bcb00cb8f..15d96029e4 100644 --- a/panda/src/mathutil/mersenne.h +++ b/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 From 51497da8fbe38985677d38f079b1cf79ae183020 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 7 Oct 2018 01:33:18 -0600 Subject: [PATCH 20/46] movies: Fix missing include --- panda/src/movies/movieTypeRegistry.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/movies/movieTypeRegistry.cxx b/panda/src/movies/movieTypeRegistry.cxx index 51468ac2a4..5bc8228bb4 100644 --- a/panda/src/movies/movieTypeRegistry.cxx +++ b/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; From 8d147056894ab4af3936aa3200a5adee23781891 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 7 Oct 2018 01:36:18 -0600 Subject: [PATCH 21/46] interrogate: Fix typo --- dtool/src/interrogate/interrogateBuilder.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dtool/src/interrogate/interrogateBuilder.cxx b/dtool/src/interrogate/interrogateBuilder.cxx index f579f1586d..15607c95ea 100644 --- a/dtool/src/interrogate/interrogateBuilder.cxx +++ b/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; } From 83753405820bcf4884d78e116eb2f8f31c4151a7 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:24:19 +0200 Subject: [PATCH 22/46] py_panda: fix TypeError being raised instead of AttributeError This prevented doing something like hasattr(vec3, "stuff") --- dtool/src/interrogatedb/py_panda.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index e900ab0e7f..42bcdb9a35 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/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; } From 8cb048022242db4be1fa18ef66fbc5fc934c107e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:25:08 +0200 Subject: [PATCH 23/46] readme: update Win build instructions to mention MSVC 2017 --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf45f0dfd2..3a088a5cc1 100644 --- a/README.md +++ b/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 From 7d3b7036acec39082abc462f11b0a0dc18f14bae Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:25:53 +0200 Subject: [PATCH 24/46] readme: suggest libassimp-dev and libopenexr-dev on Ubuntu --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a088a5cc1..14ab95e8b1 100644 --- a/README.md +++ b/README.md @@ -103,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 From 86c9d11a538d3485012de438419a1e1642b91941 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:26:24 +0200 Subject: [PATCH 25/46] readme: add Android/termux build instructions --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 14ab95e8b1..43feb9b19c 100644 --- a/README.md +++ b/README.md @@ -165,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 ============= From b0c9000000f2da908478826e8e7614dfd5438e75 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 21:41:02 +0200 Subject: [PATCH 26/46] display: fix assert when app exits before window fully opens Possibly addressing #403 --- panda/src/display/graphicsEngine.cxx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index dd637d7a9a..57a592f05d 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/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(); } From e1af4abf11ef1600ee686a8fa603506293c554c1 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 21:42:02 +0200 Subject: [PATCH 27/46] glgsg: fix sRGB for FBOs created from non-sRGB host window In this case _current_properties in begin_frame() will not have srgb_color set, as the current props are set by the host window --- panda/src/glstuff/glGraphicsBuffer_src.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/panda/src/glstuff/glGraphicsBuffer_src.cxx b/panda/src/glstuff/glGraphicsBuffer_src.cxx index 66a9e6d407..eab850bdba 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.cxx +++ b/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; From 51414466da2f241157e075c39da6ac35ac05b32d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 22:52:49 +0200 Subject: [PATCH 28/46] display: ignore material if no lights are applied This fixes materials suddenly showing up when a color scale is applied and color-scale-via-lighting is set. Fixes #404 --- panda/src/display/standardMunger.cxx | 23 ++++++++++++++++++++++- panda/src/display/standardMunger.h | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/panda/src/display/standardMunger.cxx b/panda/src/display/standardMunger.cxx index 5d2a3f5102..94f376e99b 100644 --- a/panda/src/display/standardMunger.cxx +++ b/panda/src/display/standardMunger.cxx @@ -36,7 +36,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); @@ -94,6 +95,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 +305,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 +361,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; } diff --git a/panda/src/display/standardMunger.h b/panda/src/display/standardMunger.h index 05e8ee0344..5703fd01b5 100644 --- a/panda/src/display/standardMunger.h +++ b/panda/src/display/standardMunger.h @@ -55,6 +55,7 @@ private: bool _munge_color_scale; bool _auto_shader; bool _shader_skinning; + bool _remove_material; LColor _color; LVecBase4 _color_scale; From a9ff8a22f0e76218220010189de375dd48d2741b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 23:00:17 +0200 Subject: [PATCH 29/46] makepanda: enable Assimp in default Confauto.prc if built --- makepanda/confauto.in | 5 +++++ makepanda/makepanda.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/makepanda/confauto.in b/makepanda/confauto.in index 608a6daf68..41655f1bbc 100644 --- a/makepanda/confauto.in +++ b/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. diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index e371fbd3e8..a81ea7fe56 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2882,6 +2882,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: From 80951b3268a4894903b8cff0555bc535c79a7b36 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 23:02:50 +0200 Subject: [PATCH 30/46] task: provide more properties on AsyncTask --- panda/src/event/asyncTask.h | 21 +++++++++++++++++++++ panda/src/event/pythonTask.h | 7 ------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/panda/src/event/asyncTask.h b/panda/src/event/asyncTask.h index f5871ae2f2..5a7a2cd805 100644 --- a/panda/src/event/asyncTask.h +++ b/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(); diff --git a/panda/src/event/pythonTask.h b/panda/src/event/pythonTask.h index 3771d46d8b..06ff8b6fe4 100644 --- a/panda/src/event/pythonTask.h +++ b/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__; From 102a256b05092f97a75a221b18dd3d86253a7625 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 23:03:07 +0200 Subject: [PATCH 31/46] tests: remove accidentally added debug image output --- tests/display/test_depth_buffer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/display/test_depth_buffer.py b/tests/display/test_depth_buffer.py index 8581fe47ea..c655758703 100644 --- a/tests/display/test_depth_buffer.py +++ b/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] From 914ef2e13d497c0dec444fd82ab80dd08f15de61 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 00:59:22 +0200 Subject: [PATCH 32/46] tests: add various unit tests for color-related render states --- tests/display/test_color_buffer.py | 271 +++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 tests/display/test_color_buffer.py diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py new file mode 100644 index 0000000000..dab73cbbb8 --- /dev/null +++ b/tests/display/test_color_buffer.py @@ -0,0 +1,271 @@ +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 + + +@pytest.fixture(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() + + +@pytest.fixture(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) + + +@pytest.fixture(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()) + + cm = core.CardMaker("card") + cm.set_frame(-1, 1, -1, 1) + + if vertex_color is not None: + cm.set_color(vertex_color) + + card = scene.attach_new_node(cm.generate()) + card.set_state(state) + 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): + #XXX This behaviour is really odd. + 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.almost_equal(TEST_COLOR, FUZZ) + + +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): + #XXX This behaviour is really odd. + 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_SCALED_COLOR, FUZZ) + From 93a3e7e6991d5d3143aba2ea42dfdf7bed52f431 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 14:46:07 +0200 Subject: [PATCH 33/46] Changes to make ColorAttrib behavior more consistent: - T_off now actually properly disables vertex colours - T_vertex is now the default, to preserve the previous behaviour - ShaderGenerator behavior is now the same as in the FFP - tests are updated to verify new behavior - tests now properly use vertex colours, previously they accidentally only used flat colors - With color-scale-via-lighting off and no color scale, color is no longer munged - p3d_Color in GLSL shaders is now properly set to white instead of black with T_off mode - In DX9 shaders will now sample white color for absent or disabled vertex color Fixes #401 Also see #371 --- panda/src/display/graphicsStateGuardian.cxx | 13 +++- panda/src/display/standardMunger.cxx | 20 +------ panda/src/display/standardMunger.h | 5 +- panda/src/dxgsg9/dxGeomMunger9.I | 31 ---------- panda/src/dxgsg9/dxGeomMunger9.cxx | 60 +++++++++++++++++++ panda/src/dxgsg9/dxGeomMunger9.h | 2 +- panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx | 40 +++++++++++++ panda/src/dxgsg9/dxGraphicsStateGuardian9.h | 8 +-- panda/src/dxgsg9/dxShaderContext9.cxx | 23 +++++++ .../glstuff/glGraphicsStateGuardian_src.cxx | 11 +++- panda/src/glstuff/glShaderContext_src.cxx | 4 +- panda/src/pgraph/colorAttrib.cxx | 2 +- panda/src/pgraph/colorAttrib.h | 2 +- tests/display/test_color_buffer.py | 40 ++++++++++--- 14 files changed, 188 insertions(+), 73 deletions(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 1050f8372f..b76a7f751c 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/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. diff --git a/panda/src/display/standardMunger.cxx b/panda/src/display/standardMunger.cxx index 94f376e99b..4bb96fcfd5 100644 --- a/panda/src/display/standardMunger.cxx +++ b/panda/src/display/standardMunger.cxx @@ -55,24 +55,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) { + color_attrib->get_color_type() != ColorAttrib::T_vertex) { - 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; - } + // 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()) { diff --git a/panda/src/display/standardMunger.h b/panda/src/display/standardMunger.h index 5703fd01b5..d7c71e28e5 100644 --- a/panda/src/display/standardMunger.h +++ b/panda/src/display/standardMunger.h @@ -51,12 +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; diff --git a/panda/src/dxgsg9/dxGeomMunger9.I b/panda/src/dxgsg9/dxGeomMunger9.I index cc972bf94b..2accff3b11 100644 --- a/panda/src/dxgsg9/dxGeomMunger9.I +++ b/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); -} diff --git a/panda/src/dxgsg9/dxGeomMunger9.cxx b/panda/src/dxgsg9/dxGeomMunger9.cxx index 638b121f27..0e95070c91 100644 --- a/panda/src/dxgsg9/dxGeomMunger9.cxx +++ b/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); +} + /** * */ diff --git a/panda/src/dxgsg9/dxGeomMunger9.h b/panda/src/dxgsg9/dxGeomMunger9.h index c0762e3cf3..c0cb8f8edd 100644 --- a/panda/src/dxgsg9/dxGeomMunger9.h +++ b/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); diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx index 0921350629..b18c937d81 100644 --- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx +++ b/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; @@ -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 diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.h b/panda/src/dxgsg9/dxGraphicsStateGuardian9.h index 66b2fb82d5..8852f413a7 100644 --- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.h +++ b/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 diff --git a/panda/src/dxgsg9/dxShaderContext9.cxx b/panda/src/dxgsg9/dxShaderContext9.cxx index 6513ce8a89..a737c342bf 100644 --- a/panda/src/dxgsg9/dxShaderContext9.cxx +++ b/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()) { diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 48b9112387..616784e0cc 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/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 diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index a96a07a2e8..64a5e77553 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/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 } } diff --git a/panda/src/pgraph/colorAttrib.cxx b/panda/src/pgraph/colorAttrib.cxx index 129f5a9184..f1d71845cd 100644 --- a/panda/src/pgraph/colorAttrib.cxx +++ b/panda/src/pgraph/colorAttrib.cxx @@ -68,7 +68,7 @@ make_off() { */ CPT(RenderAttrib) ColorAttrib:: make_default() { - return make_off(); + return make_vertex(); } /** diff --git a/panda/src/pgraph/colorAttrib.h b/panda/src/pgraph/colorAttrib.h index a92543c855..b652233a62 100644 --- a/panda/src/pgraph/colorAttrib.h +++ b/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(); diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py index dab73cbbb8..d95913b0f7 100644 --- a/tests/display/test_color_buffer.py +++ b/tests/display/test_color_buffer.py @@ -113,14 +113,38 @@ def render_color_pixel(region, state, vertex_color=None): camera.node().get_lens(0).set_near_far(1, 3) camera.node().set_cull_bounds(core.OmniBoundingVolume()) - cm = core.CardMaker("card") - cm.set_frame(-1, 1, -1, 1) + 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: - cm.set_color(vertex_color) + 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) - card = scene.attach_new_node(cm.generate()) - card.set_state(state) + 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) @@ -197,14 +221,13 @@ def test_color_empty_vertex(color_region, shader_attrib, material_attrib): def test_color_off_vertex(color_region, shader_attrib, material_attrib): - #XXX This behaviour is really odd. 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.almost_equal(TEST_COLOR, FUZZ) + assert result == (1, 1, 1, 1) def test_scaled_color_empty(color_region, shader_attrib, material_attrib): @@ -259,7 +282,6 @@ def test_scaled_color_empty_vertex(color_region, shader_attrib, material_attrib) def test_scaled_color_off_vertex(color_region, shader_attrib, material_attrib): - #XXX This behaviour is really odd. state = core.RenderState.make( core.ColorAttrib.make_off(), core.ColorScaleAttrib.make(TEST_COLOR_SCALE), @@ -267,5 +289,5 @@ def test_scaled_color_off_vertex(color_region, shader_attrib, material_attrib): material_attrib, ) result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR) - assert result.almost_equal(TEST_SCALED_COLOR, FUZZ) + assert result.almost_equal(TEST_COLOR_SCALE, FUZZ) From 96860b88e041d5230f6823e201d913e535f4d415 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 22:11:55 +0200 Subject: [PATCH 34/46] dxgsg9: fix problems with window without depth buffer --- panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx | 4 ++-- panda/src/dxgsg9/wdxGraphicsWindow9.cxx | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx index b18c937d81..904f47afc3 100644 --- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx +++ b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx @@ -799,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()) { diff --git a/panda/src/dxgsg9/wdxGraphicsWindow9.cxx b/panda/src/dxgsg9/wdxGraphicsWindow9.cxx index 3136d75fd0..3207c81189 100644 --- a/panda/src/dxgsg9/wdxGraphicsWindow9.cxx +++ b/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)) { From 9061fd941648056d5a00069b5bc451907a6fda79 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 22:13:08 +0200 Subject: [PATCH 35/46] dtoolutil: fix TextEncoder methods for Python 3 The no-arguments get_text() and set_text() will now return Unicode strings in Python 3, but passing in an encoding will make them return/take bytes objects. In Python 2, they all take regular strings, but Unicode is also accepted by the no-argument get_text() and set_text(). In the future we probably want to remove most of this interface for Python users, to whom all this is unnecessary since it duplicates functionality already in the standard library. --- .../dtoolutil/p3dtoolutil_ext_composite.cxx | 1 + dtool/src/dtoolutil/textEncoder.I | 25 ++- dtool/src/dtoolutil/textEncoder.cxx | 8 + dtool/src/dtoolutil/textEncoder.h | 24 +++ dtool/src/dtoolutil/textEncoder_ext.I | 30 ++++ dtool/src/dtoolutil/textEncoder_ext.cxx | 159 ++++++++++++++++++ dtool/src/dtoolutil/textEncoder_ext.h | 50 ++++++ makepanda/makepanda.py | 1 + panda/src/text/textNode.I | 77 --------- panda/src/text/textNode.cxx | 9 + panda/src/text/textNode.h | 17 +- 11 files changed, 305 insertions(+), 96 deletions(-) create mode 100644 dtool/src/dtoolutil/textEncoder_ext.I create mode 100644 dtool/src/dtoolutil/textEncoder_ext.cxx create mode 100644 dtool/src/dtoolutil/textEncoder_ext.h diff --git a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx index 89a0ebcc30..2cd825ff58 100644 --- a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx +++ b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx @@ -1,2 +1,3 @@ #include "filename_ext.cxx" #include "globPattern_ext.cxx" +#include "textEncoder_ext.cxx" diff --git a/dtool/src/dtoolutil/textEncoder.I b/dtool/src/dtoolutil/textEncoder.I index e07f489ab0..417ef386e2 100644 --- a/dtool/src/dtoolutil/textEncoder.I +++ b/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(); + } } /** @@ -163,6 +172,7 @@ INLINE void TextEncoder:: append_unicode_char(int character) { _wtext = get_wtext() + std::wstring(1, (wchar_t)character); _flags = (_flags | F_got_wtext) & ~F_got_text; + text_changed(); } /** @@ -200,6 +210,7 @@ set_unicode_char(size_t index, int character) { if (index < _wtext.length()) { _wtext[index] = character; _flags &= ~F_got_text; + text_changed(); } } @@ -418,6 +429,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 +451,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(); + } } /** diff --git a/dtool/src/dtoolutil/textEncoder.cxx b/dtool/src/dtoolutil/textEncoder.cxx index 1e1cd4bc61..da835b7bfb 100644 --- a/dtool/src/dtoolutil/textEncoder.cxx +++ b/dtool/src/dtoolutil/textEncoder.cxx @@ -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(); } /** @@ -314,6 +316,12 @@ expand_amp_sequence(StringDecoder &decoder) const { } */ +/** + * Called whenever the text has been changed. + */ +void TextEncoder:: +text_changed() { +} /** * diff --git a/dtool/src/dtoolutil/textEncoder.h b/dtool/src/dtoolutil/textEncoder.h index a7eaf395ff..baa0ef9b3e 100644 --- a/dtool/src/dtoolutil/textEncoder.h +++ b/dtool/src/dtoolutil/textEncoder.h @@ -48,17 +48,28 @@ 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); +#endif INLINE void append_unicode_char(int character); INLINE size_t get_num_chars() const; INLINE int get_unicode_char(size_t index) const; @@ -91,11 +102,24 @@ PUBLISHED: std::wstring get_wtext_as_ascii() const; bool is_wtext() const; +#ifdef CPPPARSER + EXTEND static PyObject *encode_wchar(wchar_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(wchar_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 { diff --git a/dtool/src/dtoolutil/textEncoder_ext.I b/dtool/src/dtoolutil/textEncoder_ext.I new file mode 100644 index 0000000000..2924fda011 --- /dev/null +++ b/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:: +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:: +decode_text(PyObject *text) const { + return decode_text(text, _this->get_encoding()); +} diff --git a/dtool/src/dtoolutil/textEncoder_ext.cxx b/dtool/src/dtoolutil/textEncoder_ext.cxx new file mode 100644 index 0000000000..b085b8a965 --- /dev/null +++ b/dtool/src/dtoolutil/textEncoder_ext.cxx @@ -0,0 +1,159 @@ +/** + * 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:: +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:: +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:: +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:: +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:: +append_text(PyObject *text) { + if (PyUnicode_Check(text)) { +#if PY_VERSION_HEX >= 0x03030000 + Py_ssize_t len; + const char *str = PyUnicode_AsUTF8AndSize(text, &len); + _this->append_text(std::string(str, len)); +#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:: +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:: +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:: +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 diff --git a/dtool/src/dtoolutil/textEncoder_ext.h b/dtool/src/dtoolutil/textEncoder_ext.h new file mode 100644 index 0000000000..049c16e493 --- /dev/null +++ b/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 : public ExtensionBase { +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 diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index a81ea7fe56..0164278d1e 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3551,6 +3551,7 @@ IGATEFILES += [ "dSearchPath.h", "executionEnvironment.h", "textEncoder.h", + "textEncoder_ext.h", "filename.h", "filename_ext.h", "globPattern.h", diff --git a/panda/src/text/textNode.I b/panda/src/text/textNode.I index 8070504c6b..1d02442451 100644 --- a/panda/src/text/textNode.I +++ b/panda/src/text/textNode.I @@ -1010,61 +1010,6 @@ clear_glyph_shift() { invalidate_with_measure(); } - -/** - * Changes the text that is displayed under the TextNode. - */ -INLINE void TextNode:: -set_text(const std::string &text) { - MutexHolder holder(_lock); - 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) { - MutexHolder holder(_lock); - TextEncoder::set_text(text, encoding); - invalidate_with_measure(); -} - -/** - * Removes the text from the TextNode. - */ -INLINE void TextNode:: -clear_text() { - MutexHolder holder(_lock); - 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) { - MutexHolder holder(_lock); - 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) { - MutexHolder holder(_lock); - 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. @@ -1086,28 +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) { - MutexHolder holder(_lock); - 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) { - MutexHolder holder(_lock); - 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. diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index 1ecfc1ea65..5a0c0c7f7f 100644 --- a/panda/src/text/textNode.cxx +++ b/panda/src/text/textNode.cxx @@ -319,6 +319,15 @@ get_internal_geom() const { return do_get_internal_geom(); } +/** + * Called whenever the text has been changed. + */ +void TextNode:: +text_changed() { + MutexHolder holder(_lock); + invalidate_with_measure(); +} + /** * Returns the union of all attributes from SceneGraphReducer::AttribTypes * that may not safely be applied to the vertices of this node. If this is diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h index ecf7dcafcf..02e1cb51c7 100644 --- a/panda/src/text/textNode.h +++ b/panda/src/text/textNode.h @@ -182,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; @@ -203,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; @@ -245,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); @@ -281,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, From 29b577971f71c0e6d2dfc07fb8eeeef6712d2f58 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 22:33:54 +0200 Subject: [PATCH 36/46] dtoolutil: improve Unicode encoding/decoding, support non-BMP chars - Support encoding and decoding four-byte UTF-8 sequences - E_unicode supports surrogate pairs, renamed to E_utf16be for clarity - char32_t should be used for storing a Unicode code point --- dtool/src/dtoolutil/stringDecoder.I | 2 +- dtool/src/dtoolutil/stringDecoder.cxx | 79 +++++++++++++++++++++---- dtool/src/dtoolutil/stringDecoder.h | 15 +++-- dtool/src/dtoolutil/textEncoder.I | 17 +++++- dtool/src/dtoolutil/textEncoder.cxx | 84 +++++++++++++++++++++------ dtool/src/dtoolutil/textEncoder.h | 11 ++-- 6 files changed, 167 insertions(+), 41 deletions(-) diff --git a/dtool/src/dtoolutil/stringDecoder.I b/dtool/src/dtoolutil/stringDecoder.I index f7a3b14701..ce128833d0 100644 --- a/dtool/src/dtoolutil/stringDecoder.I +++ b/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) { } diff --git a/dtool/src/dtoolutil/stringDecoder.cxx b/dtool/src/dtoolutil/stringDecoder.cxx index e77e0c5e13..f9ecfdecd3 100644 --- a/dtool/src/dtoolutil/stringDecoder.cxx +++ b/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; } diff --git a/dtool/src/dtoolutil/stringDecoder.h b/dtool/src/dtoolutil/stringDecoder.h index c0b2534ee2..6885f77e08 100644 --- a/dtool/src/dtoolutil/stringDecoder.h +++ b/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 diff --git a/dtool/src/dtoolutil/textEncoder.I b/dtool/src/dtoolutil/textEncoder.I index 417ef386e2..766319d6da 100644 --- a/dtool/src/dtoolutil/textEncoder.I +++ b/dtool/src/dtoolutil/textEncoder.I @@ -169,8 +169,23 @@ 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(); } diff --git a/dtool/src/dtoolutil/textEncoder.cxx b/dtool/src/dtoolutil/textEncoder.cxx index da835b7bfb..1065f21dcb 100644 --- a/dtool/src/dtoolutil/textEncoder.cxx +++ b/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 @@ -109,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) { @@ -145,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 ""; @@ -169,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; @@ -189,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); } @@ -213,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) { @@ -221,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(); } @@ -335,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 << ")**"; @@ -354,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) { diff --git a/dtool/src/dtoolutil/textEncoder.h b/dtool/src/dtoolutil/textEncoder.h index baa0ef9b3e..71d93a71ca 100644 --- a/dtool/src/dtoolutil/textEncoder.h +++ b/dtool/src/dtoolutil/textEncoder.h @@ -35,7 +35,10 @@ PUBLISHED: enum Encoding { E_iso8859, E_utf8, - E_unicode + E_utf16be, + + // Deprecated alias for E_utf16be + E_unicode = E_utf16be, }; INLINE TextEncoder(); @@ -70,7 +73,7 @@ PUBLISHED: INLINE std::string get_text(Encoding encoding) const; INLINE void append_text(const std::string &text); #endif - INLINE void append_unicode_char(int character); + 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); @@ -103,13 +106,13 @@ PUBLISHED: bool is_wtext() const; #ifdef CPPPARSER - EXTEND static PyObject *encode_wchar(wchar_t ch, Encoding encoding); + 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(wchar_t ch, Encoding encoding); + 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; From 0561d7920f54900fa87238cbfb8abfb27e99b965 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 23:19:29 +0200 Subject: [PATCH 37/46] tests: add unit tests for TextEncoder --- tests/dtoolutil/test_textencoder.py | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/dtoolutil/test_textencoder.py diff --git a/tests/dtoolutil/test_textencoder.py b/tests/dtoolutil/test_textencoder.py new file mode 100644 index 0000000000..ef03c7d826 --- /dev/null +++ b/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) From 7bd8cbdeb6984673fff9ba0f56402208026a5f74 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 23:20:04 +0200 Subject: [PATCH 38/46] windisplay: allow changing undecorated/fixed_size after window open Fixes #405 --- panda/src/windisplay/winGraphicsWindow.cxx | 38 ++++++++++++++++++---- panda/src/windisplay/winGraphicsWindow.h | 4 +-- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 954d8aca41..be14041027 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/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()) { diff --git a/panda/src/windisplay/winGraphicsWindow.h b/panda/src/windisplay/winGraphicsWindow.h index ff0340aa0d..5b0bb208c1 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/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); From 6f623963735ed498e9bfcfa4ed887fc2e1adbe93 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 12 Oct 2018 15:37:18 -0600 Subject: [PATCH 39/46] general: Resolve a few compiler warnings - display: GraphicsWindowProc should have a virtual destructor, as it's meant to be subclassed. - express: set_matrix_view helper should always fail an assert when 'size' is wrong, even on release builds. - express: Fix filename capitalization on some #includes. They're normally Windows-only, where case doesn't matter, but it's better to be consistent. - gobj: Fix typo. - particlesystem: Remove BaseParticle::_last_position. Last position is tracked by PhysicsObject now. - windisplay: Heed warnings about casting bool to (PVOID). Also, per MSDN docs, SPI_SETMOUSETRAILS uses the uiParam argument and ignores pvParam, so pass the _saved_mouse_trails value in that way. --- panda/src/display/graphicsWindowProc.h | 1 + panda/src/express/pointerToArray_ext.I | 2 +- panda/src/express/pointerToArray_ext.h | 10 +++++----- panda/src/gobj/textureStage.cxx | 2 +- panda/src/particlesystem/baseParticle.cxx | 1 - panda/src/particlesystem/baseParticle.h | 2 -- panda/src/windisplay/winGraphicsWindow.cxx | 8 ++++---- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/panda/src/display/graphicsWindowProc.h b/panda/src/display/graphicsWindowProc.h index c9c056eebd..a330c06a11 100644 --- a/panda/src/display/graphicsWindowProc.h +++ b/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); diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index ee4c10e155..db14284e97 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -38,7 +38,7 @@ 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); } view.len = length * mat_size; diff --git a/panda/src/express/pointerToArray_ext.h b/panda/src/express/pointerToArray_ext.h index 2daf885943..f17d1b634a 100644 --- a/panda/src/express/pointerToArray_ext.h +++ b/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; template class EXPORT_THIS Extension; diff --git a/panda/src/gobj/textureStage.cxx b/panda/src/gobj/textureStage.cxx index 4fecd20999..0d6202d949 100644 --- a/panda/src/gobj/textureStage.cxx +++ b/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; diff --git a/panda/src/particlesystem/baseParticle.cxx b/panda/src/particlesystem/baseParticle.cxx index 046b2dfb40..46e18179da 100644 --- a/panda/src/particlesystem/baseParticle.cxx +++ b/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 } diff --git a/panda/src/particlesystem/baseParticle.h b/panda/src/particlesystem/baseParticle.h index 487f9ba476..834327bfec 100644 --- a/panda/src/particlesystem/baseParticle.h +++ b/panda/src/particlesystem/baseParticle.h @@ -62,8 +62,6 @@ private: PN_stdfloat _lifespan; bool _alive; int _index; - - LPoint3 _last_position; }; #include "baseParticle.I" diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index be14041027..b42ee43992 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -2210,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; } From 84ed19e8a7d34d78060c1215aebbfede5082cca8 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 12 Oct 2018 21:49:49 -0600 Subject: [PATCH 40/46] display: Add two missing includes (lightAttrib.h and materialAttrib.h) --- panda/src/display/standardMunger.cxx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/panda/src/display/standardMunger.cxx b/panda/src/display/standardMunger.cxx index 4bb96fcfd5..2dba4aa6c0 100644 --- a/panda/src/display/standardMunger.cxx +++ b/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; From 602ea6ebf4678150531ad1520d3b7b1666b7760f Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sat, 13 Oct 2018 15:18:05 -0600 Subject: [PATCH 41/46] general: Fix a couple more compiler warnings - express: Fix a warning when compiling for debug - dtoolutil: Give TextEncoder a virtual destructor --- dtool/src/dtoolutil/textEncoder.h | 2 ++ panda/src/express/pointerToArray_ext.I | 1 + 2 files changed, 3 insertions(+) diff --git a/dtool/src/dtoolutil/textEncoder.h b/dtool/src/dtoolutil/textEncoder.h index 71d93a71ca..30004ef5d3 100644 --- a/dtool/src/dtoolutil/textEncoder.h +++ b/dtool/src/dtoolutil/textEncoder.h @@ -44,6 +44,8 @@ PUBLISHED: INLINE TextEncoder(); INLINE TextEncoder(const TextEncoder ©); + virtual ~TextEncoder() = default; + INLINE void set_encoding(Encoding encoding); INLINE Encoding get_encoding() const; diff --git a/panda/src/express/pointerToArray_ext.I b/panda/src/express/pointerToArray_ext.I index db14284e97..5bcd1b77e9 100644 --- a/panda/src/express/pointerToArray_ext.I +++ b/panda/src/express/pointerToArray_ext.I @@ -39,6 +39,7 @@ INLINE void set_matrix_view(Py_buffer &view, int flags, int length, int size, bo mat_size = sizeof(UnalignedLMatrix4d); } else { nassertv_always(false); + return; // Make sure compiler knows control flow doesn't proceed. } view.len = length * mat_size; From 2d80d6d063ef30d96d93bdd3683a634cce33b2b0 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 14 Oct 2018 15:50:06 -0600 Subject: [PATCH 42/46] general: Add missing includes and remove deprecated type Credit for missing includes to @treamology in Git commit 16cfac482923bc734447d234fd8eaaa99483847d CGTableCount removed; modern macOS seems to call this a uint32_t instead. I can find no reference to CGTableCount in any documentation, and the (very old) source code I dig up just typedefs it anyway. --- panda/src/cocoadisplay/cocoaPandaApp.mm | 1 + panda/src/display/subprocessWindow.cxx | 2 ++ panda/src/osxdisplay/osxGraphicsStateGuardian.h | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/panda/src/cocoadisplay/cocoaPandaApp.mm b/panda/src/cocoadisplay/cocoaPandaApp.mm index e7226786da..83f0b8ef42 100644 --- a/panda/src/cocoadisplay/cocoaPandaApp.mm +++ b/panda/src/cocoadisplay/cocoaPandaApp.mm @@ -12,6 +12,7 @@ */ #import "cocoaPandaApp.h" +#include "config_cocoadisplay.h" @implementation CocoaPandaApp - (void) sendEvent: (NSEvent *) event { diff --git a/panda/src/display/subprocessWindow.cxx b/panda/src/display/subprocessWindow.cxx index fe4b2eff5d..5ea85c3cc9 100644 --- a/panda/src/display/subprocessWindow.cxx +++ b/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; diff --git a/panda/src/osxdisplay/osxGraphicsStateGuardian.h b/panda/src/osxdisplay/osxGraphicsStateGuardian.h index 038ba42055..96c86f28dd 100644 --- a/panda/src/osxdisplay/osxGraphicsStateGuardian.h +++ b/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: From d7f19b73e0f903a6c7341716822d16219e0075e9 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 15 Oct 2018 13:27:36 +0200 Subject: [PATCH 43/46] dtoolutil: minor fix to TextEncoder::append_text --- dtool/src/dtoolutil/textEncoder_ext.cxx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dtool/src/dtoolutil/textEncoder_ext.cxx b/dtool/src/dtoolutil/textEncoder_ext.cxx index b085b8a965..1947e6c749 100644 --- a/dtool/src/dtoolutil/textEncoder_ext.cxx +++ b/dtool/src/dtoolutil/textEncoder_ext.cxx @@ -94,7 +94,12 @@ append_text(PyObject *text) { #if PY_VERSION_HEX >= 0x03030000 Py_ssize_t len; const char *str = PyUnicode_AsUTF8AndSize(text, &len); - _this->append_text(std::string(str, 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)); From 02a72d4273dcd580b68c7332683303cdca200483 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 15 Oct 2018 13:30:22 +0200 Subject: [PATCH 44/46] makepanda: remove reference to QuickTime framework Doesn't appear to be necessary, and QuickTime framework is removed in Mojave. Fixes #412 --- makepanda/makepanda.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 0164278d1e..dedd90b059 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -946,8 +946,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") From a765c32baef8da0185f4551945f27bd8ffa5f289 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 15 Oct 2018 13:32:22 +0200 Subject: [PATCH 45/46] makepanda: don't use -fno-rtti on macOS, it fails to compile It appears that (included by ) uses RTTI, so we can't enable this for now. --- makepanda/makepanda.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index dedd90b059..540d9e86cf 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1332,9 +1332,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': From 70f4c1cd4e08918c86d809316f14618e5560087e Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 15 Oct 2018 14:09:25 +0200 Subject: [PATCH 46/46] glgsg: change missing Cg attrib error message to debug This can occur if a variable is optimized out by the GLSL compiler but not by the Cg compiler. So it should not be reported as an error. Fixes #417 --- panda/src/glstuff/glCgShaderContext_src.cxx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/panda/src/glstuff/glCgShaderContext_src.cxx b/panda/src/glstuff/glCgShaderContext_src.cxx index 3a9b2fea6a..649208fb47 100644 --- a/panda/src/glstuff/glCgShaderContext_src.cxx +++ b/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