Browse Source

Merge branch 'release/1.10.x'

rdb 1 year ago
parent
commit
ad57762e9f

+ 2 - 1
BACKERS.md

@@ -4,10 +4,11 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 ## Bronze Sponsors
 
-[<img src="https://www.panda3d.org/wp-content/uploads/2021/02/changecrab_logo.png" alt="ChangeCrab" height="48">](https://changecrab.com/) ![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
+[<img src="https://www.panda3d.org/wp-content/uploads/2024/08/Route4MeLogo1185x300-2-1-1024x259.png" alt="Route4Me" height="48">](https://route4me.com/)
 
 * [Daniel Stokes](https://opencollective.com/daniel-stokes)
 * [David Rose](https://opencollective.com/david-rose)
+* [Route4Me](https://route4me.com/)
 
 ## Benefactors
 

+ 158 - 0
dtool/src/dtoolbase/pdtoa.cxx

@@ -428,3 +428,161 @@ void pdtoa(double value, char *buffer) {
     Prettify(buffer, length, K);
   }
 }
+
+/**
+ * Version of pdtoa that tries hard to find the minimal string representation
+ * for a single-precision floating-point number.
+ */
+void pftoa(float value, char *buffer) {
+#ifdef _MSC_VER
+  if (copysign(1.0f, value) < 0) {
+#else
+  if (std::signbit(value)) {
+#endif
+    *buffer++ = '-';
+    value = -value;
+  }
+  if (cinf(value)) {
+    buffer[0] = 'i';
+    buffer[1] = 'n';
+    buffer[2] = 'f';
+    buffer[3] = '\0';
+  } else if (cnan(value)) {
+    buffer[0] = 'n';
+    buffer[1] = 'a';
+    buffer[2] = 'n';
+    buffer[3] = '\0';
+  } else if (value == 0.0f) {
+    buffer[0] = '0';
+    buffer[1] = '.';
+    buffer[2] = '0';
+    buffer[3] = '\0';
+  } else if (value == 1.0f) {
+    buffer[0] = '1';
+    buffer[1] = '.';
+    buffer[2] = '0';
+    buffer[3] = '\0';
+  } else {
+    int length, k;
+    Grisu2(value, buffer, &length, &k);
+
+    const int kk = length + k;  // 10^(kk-1) <= v < 10^kk
+
+    if (length <= kk && kk <= 21) {
+      // 1234e7 -> 12340000000
+      for (int i = length; i < kk; i++)
+        buffer[i] = '0';
+      buffer[kk] = '.';
+      buffer[kk + 1] = '0';
+      buffer[kk + 2] = '\0';
+    }
+    else if (0 < kk && kk <= 21) {
+      // 1234e-2 -> 12.34
+      memmove(&buffer[kk + 1], &buffer[kk], length - kk);
+
+      // We want the shortest possible representation, so keep reading digits
+      // until strtod would give the correct float value.
+      buffer[kk] = '\0';
+      double v = (double)atoi(buffer);
+      buffer[kk] = '.';
+
+      double multiplicand = 0.1;
+      for (int i = kk + 1; i <= length; ++i) {
+        double vplus = v + (buffer[i] - '0' + 1) * multiplicand;
+        v += (buffer[i] - '0') * multiplicand;
+        multiplicand *= 0.1;
+
+        if ((float)v == value) {
+          length = i;
+          break;
+        }
+        if (buffer[i] < '9' && (float)vplus == value) {
+          ++buffer[i];
+          length = i;
+          break;
+        }
+      }
+
+      buffer[length + 1] = '\0';
+    }
+    else if (-6 < kk && kk <= 0) {
+      // 1234e-6 -> 0.001234
+      const int offset = 2 - kk;
+      memmove(&buffer[offset], &buffer[0], length);
+      buffer[0] = '0';
+      buffer[1] = '.';
+
+      // We want the shortest possible representation, so keep reading digits
+      // until strtod would give the correct float value.
+      double multiplicand = 1.0;
+      for (int i = 2; i < offset; i++) {
+        buffer[i] = '0';
+        multiplicand *= 0.1;
+      }
+      if ((float)multiplicand == value) {
+        length = 0;
+        buffer[offset - 1] = '1';
+      } else {
+        multiplicand *= 0.1;
+        double v = 0.0;
+        for (int i = offset; i < length + offset; ++i) {
+          double vplus = v + (buffer[i] - '0' + 1) * multiplicand;
+          v += (buffer[i] - '0') * multiplicand;
+          multiplicand *= 0.1;
+
+          if ((float)v == value) {
+            buffer[i + 1] = '\0';
+            break;
+          }
+          if (buffer[i] < '9' && (float)vplus == value) {
+            buffer[i]++;
+            buffer[i + 1] = '\0';
+            break;
+          }
+        }
+      }
+      buffer[length + offset] = '\0';
+    }
+    else if (length == 1) {
+      // 1e30
+      buffer[1] = 'e';
+      WriteExponent(kk - 1, &buffer[2]);
+    }
+    else {
+      // 1234e30 -> 1.234e33
+      memmove(&buffer[2], &buffer[1], length - 1);
+      buffer[1] = '.';
+      buffer[length + 1] = 'e';
+
+      double e_mult = pow(10.0, kk - 1);
+      if ((float)(10.0 * e_mult) == value) {
+        buffer[0] = '1';
+        buffer[1] = 'e';
+        WriteExponent(kk, &buffer[2]);
+      } else {
+        // We want the shortest possible representation, so keep reading
+        // digits until strtod would give the correct float value.
+        double v = buffer[0] - '0';
+        double multiplicand = 0.1;
+        for (int i = 2; i < length + 2; ++i) {
+          double vplus = v + (buffer[i] - '0' + 1) * multiplicand;
+          v += (buffer[i] - '0') * multiplicand;
+          multiplicand *= 0.1;
+
+          if ((float)(v * e_mult) == value) {
+            length = i;
+            buffer[i + 1] = 'e';
+            break;
+          }
+          if (buffer[i] < '9' && (float)(vplus * e_mult) == value) {
+            buffer[i]++;
+            length = i;
+            buffer[i + 1] = 'e';
+            break;
+          }
+        }
+        WriteExponent(kk - 1, &buffer[0 + length + 2]);
+      }
+    }
+  }
+}

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

@@ -16,6 +16,7 @@ extern "C" {
 #endif
 
 EXPCL_DTOOL_DTOOLBASE void pdtoa(double value, char *buffer);
+EXPCL_DTOOL_DTOOLBASE void pftoa(float value, char *buffer);
 
 #ifdef __cplusplus
 };  /* end of extern "C" */

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

@@ -32,7 +32,7 @@ format_string(bool value) {
 INLINE std::string
 format_string(float value) {
   char buffer[32];
-  pdtoa((double)value, buffer);
+  pftoa(value, buffer);
   return std::string(buffer);
 }
 

+ 10 - 3
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -922,12 +922,19 @@ write_prototypes(ostream &out_code, ostream *out_h) {
 
   // Write out a table of the externally imported types that will be filled in
   // upon module initialization.
-  if (!_external_imports.empty()) {
+  std::vector<CPPType *> ext_imports(_external_imports.begin(), _external_imports.end());
+  if (!ext_imports.empty()) {
     out_code << "#ifndef LINK_ALL_STATIC\n";
     out_code << "static Dtool_TypeDef imports[] = {\n";
 
+    // Ensure that there is a deterministic ordering of external imports.
+    std::sort(ext_imports.begin(), ext_imports.end(),
+              [] (const CPPType *a, const CPPType *b) {
+      return a->get_local_name(&parser) < b->get_local_name(&parser);
+    });
+
     int idx = 0;
-    for (CPPType *type : _external_imports) {
+    for (CPPType *type : ext_imports) {
       string class_name = type->get_local_name(&parser);
       string safe_name = make_safe_name(class_name);
 
@@ -940,7 +947,7 @@ write_prototypes(ostream &out_code, ostream *out_h) {
     out_code << "#endif\n\n";
   }
 
-  for (CPPType *type : _external_imports) {
+  for (CPPType *type : ext_imports) {
     string class_name = type->get_local_name(&parser);
     string safe_name = make_safe_name(class_name);
 

+ 3 - 0
dtool/src/interrogatedb/py_panda.h

@@ -162,6 +162,9 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
   Py_TYPE(self)->tp_free(self);\
 }
 
+// Extract the PyTypeObject pointer corresponding to a Dtool_PyTypedObject.
+#define Dtool_GetPyTypeObject(type) (&(type)->_PyType)
+
 // Use DtoolInstance_Check to check whether a PyObject* is a DtoolInstance.
 #define DtoolInstance_Check(obj) \
   (Py_TYPE(obj)->tp_basicsize >= (int)sizeof(Dtool_PyInstDef) && \

+ 1 - 1
makepanda/makepanda.py

@@ -3002,7 +3002,7 @@ if not PkgSkip("PYTHON"):
 # This is just some basic stuff since setuptools just needs this file to
 # exist, otherwise it will not read the entry_points.txt file.  Maybe we will
 # eventually want to merge this with the metadata generator in makewheel.py.
-METADATA = """Metadata-Version: 2.0
+METADATA = """Metadata-Version: 2.1
 Name: Panda3D
 Version: {version}
 License: BSD

+ 1 - 1
makepanda/makewheel.py

@@ -105,7 +105,7 @@ PROJECT_URLS = dict([line.split('=', 1) for line in GetMetadataValue('project_ur
 METADATA = {
     "license": GetMetadataValue('license'),
     "name": GetMetadataValue('name'),
-    "metadata_version": "2.0",
+    "metadata_version": "2.1",
     "generator": "makepanda",
     "summary": GetMetadataValue('description'),
     "extensions": {

+ 13 - 1
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -16,6 +16,18 @@
 using std::max;
 using std::min;
 
+/**
+ * Returns a copy of the FrameBufferProperties, but without back buffers.
+ * This is used since the GraphicsOutput constructor makes some decisions
+ * based on whether this is set, so we need to unset it early.
+ */
+static FrameBufferProperties
+without_back_buffers(const FrameBufferProperties &fb_prop) {
+  FrameBufferProperties copy(fb_prop);
+  copy.set_back_buffers(0);
+  return copy;
+}
+
 TypeHandle CLP(GraphicsBuffer)::_type_handle;
 
 /**
@@ -29,7 +41,7 @@ CLP(GraphicsBuffer)(GraphicsEngine *engine, GraphicsPipe *pipe,
                     int flags,
                     GraphicsStateGuardian *gsg,
                     GraphicsOutput *host) :
-  GraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
+  GraphicsBuffer(engine, pipe, name, without_back_buffers(fb_prop), win_prop, flags, gsg, host),
   _requested_multisamples(0),
   _requested_coverage_samples(0),
   _rb_context(nullptr),

+ 13 - 0
panda/src/linmath/dblnames.h

@@ -33,6 +33,7 @@
 #undef FLOATTYPE_IS_INT
 #undef STRINGIFY
 #undef FLOATNAME_STR
+#undef FLOATTYPE_REPR
 
 #define FLOATTYPE double
 #define FLOATNAME(ARG) ARG##d
@@ -41,3 +42,15 @@
 
 #define STRINGIFY(ARG) #ARG
 #define FLOATNAME_STR(ARG) STRINGIFY(ARG##d)
+
+#define FLOATTYPE_REPR(v, str) do { \
+  double v_copy = (v); \
+  char *into_str = (str); \
+  if ((double)(long long)v_copy == v_copy) { \
+    snprintf(into_str, 32, "%lld", (long long)v_copy); \
+  } else { \
+    pdtoa(v_copy, into_str); \
+  } \
+} while (0)
+
+#include "pdtoa.h"

+ 13 - 0
panda/src/linmath/fltnames.h

@@ -33,6 +33,7 @@
 #undef FLOATTYPE_IS_INT
 #undef STRINGIFY
 #undef FLOATNAME_STR
+#undef FLOATTYPE_REPR
 
 #define FLOATTYPE float
 #define FLOATNAME(ARG) ARG##f
@@ -41,3 +42,15 @@
 
 #define STRINGIFY(ARG) #ARG
 #define FLOATNAME_STR(ARG) STRINGIFY(ARG##f)
+
+#define FLOATTYPE_REPR(v, str) do { \
+  float v_copy = (v); \
+  char *into_str = (str); \
+  if ((float)(int)v_copy == v_copy) { \
+    snprintf(into_str, 32, "%d", (int)v_copy); \
+  } else { \
+    pftoa(v_copy, into_str); \
+  } \
+} while (0)
+
+#include "pdtoa.h"

+ 3 - 0
panda/src/linmath/intnames.h

@@ -33,6 +33,7 @@
 #undef FLOATTYPE_IS_INT
 #undef STRINGIFY
 #undef FLOATNAME_STR
+#undef FLOATTYPE_REPR
 
 #define FLOATTYPE int
 #define FLOATNAME(ARG) ARG##i
@@ -42,3 +43,5 @@
 
 #define STRINGIFY(ARG) #ARG
 #define FLOATNAME_STR(ARG) STRINGIFY(ARG##i)
+
+#define FLOATTYPE_REPR(v, str) (snprintf((str), 12, "%d", (v)))

+ 15 - 12
panda/src/linmath/lmatrix3_ext_src.I

@@ -47,18 +47,21 @@ __rmul__(FLOATTYPE scalar) const {
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LMatrix3)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LMatrix3" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_m(0, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(0, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(0, 2)) << ", "
+  char buf[32 * 17] = "LMatrix4";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_m(0, 0), p);
+  p += strlen(p);
 
-      << MAYBE_ZERO(_this->_m(1, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(1, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(1, 2)) << ", "
+  for (int i = 1; i < 9; ++i) {
+    *(p++) = ',';
+    *(p++) = ' ';
+    FLOATTYPE_REPR(_this->get_data()[i], p);
+    p += strlen(p);
+  }
 
-      << MAYBE_ZERO(_this->_m(2, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 2)) << ")";
-  return out.str();
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }

+ 15 - 20
panda/src/linmath/lmatrix4_ext_src.I

@@ -48,26 +48,21 @@ __rmul__(FLOATTYPE scalar) const {
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LMatrix4)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LMatrix4" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_m(0, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(0, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(0, 2)) << ", "
-      << MAYBE_ZERO(_this->_m(0, 3)) << ", "
+  char buf[32 * 17] = "LMatrix4";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_m(0, 0), p);
+  p += strlen(p);
 
-      << MAYBE_ZERO(_this->_m(1, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(1, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(1, 2)) << ", "
-      << MAYBE_ZERO(_this->_m(1, 3)) << ", "
-
-      << MAYBE_ZERO(_this->_m(2, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 2)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 3)) << ", "
+  for (int i = 1; i < 16; ++i) {
+    *(p++) = ',';
+    *(p++) = ' ';
+    FLOATTYPE_REPR(_this->get_data()[i], p);
+    p += strlen(p);
+  }
 
-      << MAYBE_ZERO(_this->_m(3, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(3, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(3, 2)) << ", "
-      << MAYBE_ZERO(_this->_m(3, 3)) << ")";
-  return out.str();
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }

+ 13 - 5
panda/src/linmath/lpoint2_ext_src.I

@@ -16,11 +16,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint2)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LPoint2" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ")";
-  return out.str();
+  char buf[96] = "LPoint2";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 17 - 6
panda/src/linmath/lpoint3_ext_src.I

@@ -16,12 +16,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint3)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LPoint3" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ")";
-  return out.str();
+  char buf[128] = "LPoint3";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 21 - 7
panda/src/linmath/lpoint4_ext_src.I

@@ -16,13 +16,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint4)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LPoint4" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ", "
-      << MAYBE_ZERO(_this->_v(3)) << ")";
-  return out.str();
+  char buf[160] = "LPoint4";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(3), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 13 - 5
panda/src/linmath/lvecBase2_ext_src.I

@@ -24,11 +24,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase2)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVecBase2" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ")";
-  return out.str();
+  char buf[96] = "LVecBase2";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 18 - 6
panda/src/linmath/lvecBase3_ext_src.I

@@ -24,12 +24,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase3)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVecBase3" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ")";
-  return out.str();
+  char buf[128] = "LVecBase3";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**
@@ -408,3 +419,4 @@ __getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
 
 #undef PYNUMBER_FLOATTYPE
 #undef PY_AS_FLOATTYPE
+#undef FLOATTYPE_TO_STR

+ 21 - 7
panda/src/linmath/lvecBase4_ext_src.I

@@ -24,13 +24,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase4)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVecBase4" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ", "
-      << MAYBE_ZERO(_this->_v(3)) << ")";
-  return out.str();
+  char buf[160] = "LVecBase4";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(3), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 13 - 5
panda/src/linmath/lvector2_ext_src.I

@@ -16,11 +16,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector2)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVector2" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ")";
-  return out.str();
+  char buf[96] = "LVector2";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 17 - 6
panda/src/linmath/lvector3_ext_src.I

@@ -16,12 +16,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector3)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVector3" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ")";
-  return out.str();
+  char buf[128] = "LVector3";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 21 - 7
panda/src/linmath/lvector4_ext_src.I

@@ -16,13 +16,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector4)>::
 __repr__() const {
-  std::ostringstream out;
-  out << "LVector4" << FLOATTOKEN << "("
-      << MAYBE_ZERO(_this->_v(0)) << ", "
-      << MAYBE_ZERO(_this->_v(1)) << ", "
-      << MAYBE_ZERO(_this->_v(2)) << ", "
-      << MAYBE_ZERO(_this->_v(3)) << ")";
-  return out.str();
+  char buf[160] = "LVector4";
+  char *p = buf + strlen(buf);
+  *(p++) = FLOATTOKEN;
+  *(p++) = '(';
+  FLOATTYPE_REPR(_this->_v(0), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(1), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(2), p);
+  p += strlen(p);
+  *(p++) = ',';
+  *(p++) = ' ';
+  FLOATTYPE_REPR(_this->_v(3), p);
+  p += strlen(p);
+  *(p++) = ')';
+  *p = '\0';
+  return std::string(buf, p - buf);
 }
 
 /**

+ 9 - 0
panda/src/x11display/x11GraphicsWindow.cxx

@@ -1321,6 +1321,15 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) {
     if (XStringListToTextProperty((char **)&name, 1, &window_name) != 0) {
       window_name_p = &window_name;
     }
+
+#ifdef X_HAVE_UTF8_STRING
+    XTextProperty wm_name;
+    if (Xutf8TextListToTextProperty(_display, (char **)&name, 1,
+                                    XUTF8StringStyle, &wm_name) == Success) {
+      XSetTextProperty(_display, _xwindow, &wm_name, x11_pipe->_net_wm_name);
+      XFree(wm_name.value);
+    }
+#endif
   }
 
   // The size hints request a window of a particular size andor a particular

+ 11 - 0
tests/linmath/test_lvector2.py

@@ -145,6 +145,17 @@ def test_vec2_floordiv(type):
             assert v.x == i // -j
 
 
+def test_vec2_repr():
+    assert repr(Vec2F(0.1, 0.2)) == "LVector2f(0.1, 0.2)"
+    assert repr(Vec2F(0.3, 0.4)) == "LVector2f(0.3, 0.4)"
+    assert repr(Vec2F(-0.9999999403953552, 1.00000001)) == "LVector2f(-0.99999994, 1)"
+    assert repr(Vec2F(0.00000001, 0.0)) == "LVector2f(1e-8, 0)"
+    assert repr(Vec2D(0.1, 0.2)) == "LVector2d(0.1, 0.2)"
+    assert repr(Vec2D(0.3, 0.4)) == "LVector2d(0.3, 0.4)"
+    assert repr(Vec2D(-0.9999999403953552, 1.00000001)) == "LVector2d(-0.9999999403953552, 1.00000001)"
+    assert repr(Vec2D(0.00000001, 0.0)) == "LVector2d(1e-8, 0)"
+
+
 def test_vec2_buffer():
     v = Vec2(1.5, -10.0)
     m = memoryview(v)

+ 13 - 0
tests/linmath/test_lvector3.py

@@ -130,6 +130,19 @@ def test_vec3_floordiv(type):
             assert v.x == i // -j
 
 
+def test_vec3_repr():
+    assert repr(Vec3F(0.1, 0.2, 0.3)) == "LVector3f(0.1, 0.2, 0.3)"
+    assert repr(Vec3F(-0.9999999403953552, 1.00000001, 1)) == "LVector3f(-0.99999994, 1, 1)"
+    assert repr(Vec3F(-9.451235e29, 9.451234e-19, 1e-11)) == "LVector3f(-9.451235e29, 9.451234e-19, 1e-11)"
+    assert repr(Vec3F(0.001, 0.0001, 0.00001)) == "LVector3f(0.001, 0.0001, 0.00001)"
+    assert repr(Vec3F(-0.001, -0.0001, -0.00001)) == "LVector3f(-0.001, -0.0001, -0.00001)"
+    assert repr(Vec3D(0.1, 0.2, 0.3)) == "LVector3d(0.1, 0.2, 0.3)"
+    assert repr(Vec3D(-0.9999999403953552, 1.00000001, 1)) == "LVector3d(-0.9999999403953552, 1.00000001, 1)"
+    assert repr(Vec3D(-9.451235e29, 9.451234e-19, 1e-11)) == "LVector3d(-9.451235e29, 9.451234e-19, 1e-11)"
+    assert repr(Vec3D(0.001, 0.0001, 0.00001)) == "LVector3d(0.001, 0.0001, 0.00001)"
+    assert repr(Vec3D(-0.001, -0.0001, -0.00001)) == "LVector3d(-0.001, -0.0001, -0.00001)"
+
+
 def test_vec3_buffer():
     v = Vec3(0.5, 2.0, -10.0)
     m = memoryview(v)

+ 7 - 0
tests/linmath/test_lvector4.py

@@ -146,6 +146,13 @@ def test_vec4_floordiv(type):
             assert v.x == i // -j
 
 
+def test_vec4_repr():
+    assert repr(Vec4F(0.1, 0.2, 0.3, 0.4)) == "LVector4f(0.1, 0.2, 0.3, 0.4)"
+    assert repr(Vec4F(-0.9999999403953552, 1.000001, 1e-8, 0.0)) == "LVector4f(-0.99999994, 1.000001, 1e-8, 0)"
+    assert repr(Vec4D(0.1, 0.2, 0.3, 0.4)) == "LVector4d(0.1, 0.2, 0.3, 0.4)"
+    assert repr(Vec4D(-0.9999999403953552, 1.00000001, 1e-8, 0.0)) == "LVector4d(-0.9999999403953552, 1.00000001, 1e-8, 0)"
+
+
 def test_vec4_buffer():
     v = Vec4(0, 0.5, 2.0, -4.0)
     m = memoryview(v)

+ 1 - 1
tests/showbase/test_Loader.py

@@ -90,7 +90,7 @@ class FnargleLoader:
 """)
     (tmp_path / "fnargle.dist-info").mkdir()
     (tmp_path / "fnargle.dist-info" / "METADATA").write_text("""
-Metadata-Version: 2.0
+Metadata-Version: 2.1
 Name: fnargle
 Version: 1.0.0
 """)