Browse Source

Merge remote-tracking branch 'origin/master' into webgl-port

rdb 1 year ago
parent
commit
e8a226ed80
53 changed files with 754 additions and 442 deletions
  1. 0 39
      .github/workflows/lint.yml
  2. 0 77
      .github/workflows/review.yml
  3. 1 1
      README.md
  4. 4 2
      direct/src/dist/commands.py
  5. 6 1
      direct/src/gui/DirectGuiBase.py
  6. 90 0
      doc/ReleaseNotes
  7. 296 246
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  8. 4 2
      dtool/src/interrogate/interfaceMakerPythonNative.h
  9. 36 1
      dtool/src/interrogatedb/py_panda.cxx
  10. 6 1
      dtool/src/interrogatedb/py_panda.h
  11. 12 0
      makepanda/installer.nsi
  12. 7 2
      makepanda/makepandacore.py
  13. 5 0
      panda/src/bullet/bulletContactCallbacks.h
  14. 5 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  15. 4 0
      panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.mm
  16. 1 1
      panda/src/device/inputDeviceNode.cxx
  17. 20 3
      panda/src/display/graphicsPipeSelection.cxx
  18. 1 0
      panda/src/display/graphicsPipeSelection.h
  19. 1 1
      panda/src/express/multifile.I
  20. 5 3
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  21. 41 9
      panda/src/glstuff/glShaderContext_src.cxx
  22. 2 2
      panda/src/glstuff/glShaderContext_src.h
  23. 4 4
      panda/src/gobj/texture.I
  24. 5 0
      panda/src/grutil/meshDrawer.cxx
  25. 2 1
      panda/src/pgraph/lensNode.cxx
  26. 19 0
      panda/src/pgui/pgEntry.h
  27. 6 0
      panda/src/pgui/pgFrameStyle.h
  28. 12 0
      panda/src/pgui/pgItem.h
  29. 11 0
      panda/src/pgui/pgScrollFrame.h
  30. 19 0
      panda/src/pgui/pgSliderBar.h
  31. 3 0
      panda/src/pgui/pgTop.h
  32. 4 0
      panda/src/pgui/pgVirtualFrame.h
  33. 3 0
      panda/src/pgui/pgWaitBar.h
  34. 9 6
      panda/src/pipeline/thread.cxx
  35. 5 3
      panda/src/pipeline/threadDummyImpl.I
  36. 1 1
      panda/src/pipeline/threadDummyImpl.h
  37. 8 3
      panda/src/pipeline/threadPosixImpl.cxx
  38. 1 1
      panda/src/pipeline/threadPosixImpl.h
  39. 4 2
      panda/src/pipeline/threadSimpleImpl.I
  40. 1 1
      panda/src/pipeline/threadSimpleImpl.h
  41. 8 3
      panda/src/pipeline/threadWin32Impl.cxx
  42. 1 1
      panda/src/pipeline/threadWin32Impl.h
  43. 2 2
      panda/src/putil/bamReaderParam.I
  44. 3 3
      panda/src/putil/bamReaderParam.h
  45. 2 4
      panda/src/recorder/mouseRecorder.cxx
  46. 2 4
      panda/src/recorder/socketStreamRecorder.cxx
  47. 4 0
      panda/src/text/config_text.cxx
  48. 1 1
      panda/src/text/fontPool.I
  49. 41 0
      panda/src/text/textAssembler.cxx
  50. 8 8
      panda/src/windisplay/winGraphicsWindow.cxx
  51. 1 1
      pandatool/src/egg-palettize/txaFileFilter.cxx
  52. 2 2
      tests/display/test_cg_shader.py
  53. 15 0
      tests/gui/test_DirectButton.py

+ 0 - 39
.github/workflows/lint.yml

@@ -1,39 +0,0 @@
-name: Lint
-on: [pull_request]
-
-jobs:
-  clang-tidy:
-    runs-on: ubuntu-20.04
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        fetch-depth: 2
-    - name: Install clang-tidy
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y clang-tidy build-essential pkg-config libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev libgtk-3-dev libassimp-dev libopenexr-dev
-    - name: Prepare compile_commands.json
-      run: |
-        cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_UNITY_BUILD=OFF -DHAVE_PYTHON=OFF -DINTERROGATE_PYTHON_INTERFACE=OFF
-    - name: Copy prebuilt files
-      run: |
-        for fn in **/*.prebuilt; do
-          echo mkdir -p $(dirname "cmake/$fn");
-          echo cp "$fn" "cmake/${fn%.*}";
-        done
-    - name: Create results directory
-      run: |
-        mkdir clang-tidy-result
-    - name: Analyze
-      run: |
-        git diff -U0 HEAD^ | clang-tidy-diff -p1 -path build -export-fixes clang-tidy-result/fixes.yml
-    - name: Save PR metadata
-      run: |
-        echo ${{ github.event.number }} > clang-tidy-result/pr-id.txt
-        echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt
-        echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt
-    - name: Upload results
-      uses: actions/upload-artifact@v2
-      with:
-        name: clang-tidy-result
-        path: clang-tidy-result/

+ 0 - 77
.github/workflows/review.yml

@@ -1,77 +0,0 @@
-name: Post PR Review
-
-on:
-  workflow_run:
-    workflows: ["Lint"]
-    types: [completed]
-
-jobs:
-  clang-tidy-results:
-    # Trigger the job only if the previous (insecure) workflow completed successfully
-    if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
-    runs-on: ubuntu-20.04
-    steps:
-    - name: Download analysis results
-      uses: actions/[email protected]
-      with:
-        script: |
-          let artifacts = await github.actions.listWorkflowRunArtifacts({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              run_id: ${{ github.event.workflow_run.id }},
-          });
-          let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
-              return artifact.name == "clang-tidy-result"
-          })[0];
-          let download = await github.actions.downloadArtifact({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              artifact_id: matchArtifact.id,
-              archive_format: "zip",
-          });
-          let fs = require("fs");
-          fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
-    - name: Set environment variables
-      run: |
-        mkdir clang-tidy-result
-        unzip clang-tidy-result.zip -d clang-tidy-result
-        echo "pr_id=$(cat clang-tidy-result/pr-id.txt)" >> $GITHUB_ENV
-        echo "pr_head_repo=$(cat clang-tidy-result/pr-head-repo.txt)" >> $GITHUB_ENV
-        echo "pr_head_ref=$(cat clang-tidy-result/pr-head-ref.txt)" >> $GITHUB_ENV
-    - uses: actions/checkout@v2
-      with:
-        repository: ${{ env.pr_head_repo }}
-        ref: ${{ env.pr_head_ref }}
-        persist-credentials: false
-    - name: Redownload analysis results
-      uses: actions/[email protected]
-      with:
-        script: |
-          let artifacts = await github.actions.listWorkflowRunArtifacts({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              run_id: ${{github.event.workflow_run.id}},
-          });
-          let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
-              return artifact.name == "clang-tidy-result"
-          })[0];
-          let download = await github.actions.downloadArtifact({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              artifact_id: matchArtifact.id,
-              archive_format: "zip",
-          });
-          let fs = require("fs");
-          fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
-    - name: Extract analysis results
-      run: |
-        mkdir clang-tidy-result
-        unzip clang-tidy-result.zip -d clang-tidy-result
-    - name: Run clang-tidy-pr-comments action
-      uses: platisd/clang-tidy-pr-comments@master
-      with:
-        github_token: ${{ github.token }}
-        clang_tidy_fixes: clang-tidy-result/fixes.yml
-        pull_request_id: ${{ env.pr_id }}
-        request_changes: true
-        suggestions_per_comment: 10

+ 1 - 1
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-13/).
+[this page](https://www.panda3d.org/download/sdk-1-10-14/).
 If you are familiar with installing Python packages, you can use
 the following command:
 

+ 4 - 2
direct/src/dist/commands.py

@@ -471,8 +471,10 @@ class build_apps(setuptools.Command):
         if self.bam_model_extensions:
             for ext in self.bam_model_extensions:
                 ext = '.' + ext.lstrip('.')
-                assert ext not in self.file_handlers, \
-                    'Extension {} occurs in both file_handlers and bam_model_extensions!'.format(ext)
+                handler = self.file_handlers.get(ext)
+                if handler != _model_to_bam:
+                    assert handler is None, \
+                        'Extension {} occurs in both file_handlers and bam_model_extensions!'.format(ext)
                 self.file_handlers[ext] = _model_to_bam
 
         tmp = self.default_file_handlers.copy()

+ 6 - 1
direct/src/gui/DirectGuiBase.py

@@ -229,7 +229,12 @@ class DirectGuiBase(DirectObject.DirectObject):
                         del keywords[name]
                     else:
                         # Use optionDefs value
-                        optionInfo[name] = [default, default, function]
+                        value = default
+                        if isinstance(value, list):
+                            value = list(value)
+                        elif isinstance(value, dict):
+                            value = dict(value)
+                        optionInfo[name] = [default, value, function]
                 elif optionInfo[name][FUNCTION] is None:
                     # Only override function if not defined by derived class
                     optionInfo[name][FUNCTION] = function

+ 90 - 0
doc/ReleaseNotes

@@ -1,3 +1,93 @@
+-----------------------  RELEASE 1.10.14  -----------------------
+
+This release adds support for Python 3.12 and furthermore contains significant
+bug fixes, and also implements some missing features in the shader generator.
+
+API
+* Accept bytes object in DatagramOutputFile.write_header()
+* Add missing clear_color() method to CardMaker
+* Add file_version properties to BamFile and BamWriter, mirroring BamReader
+* Add missing method for getting current display mode index (#1550)
+
+Shader Generator
+* Add support for perspective points (#1440)
+* Implement remaining missing TexGenAttrib modes (#1437)
+* Fix support for hardware point sprites
+* Support missing texture types: cube map arrays, 1D arrays, buffer textures
+
+Rendering
+* Add texconst_i input for Cg shaders to access TexGenAttrib constants
+* Fix bug when same texture is used with different TexGenAttrib modes
+* OpenGL 3.2+/ES: Correctly handle 4-component texcoords in default shader
+* DX9: fix bugs setting some kinds of shader inputs
+* OpenGL: guard against exceeding max supported vertex attribute stride
+* Add missing handling for 1D array textures in Cg shaders
+* OpenGL: improve handling of SM5 Cg shaders
+* Fix p3d_LightModel.ambient not updating properly
+* Work around wireframe rendering bug in Panfrost drivers
+* Fix assertion error when preparing texture fails
+* Fix image load/store support in GLES 3.1 (set gl-immutable-texture-storage)
+
+Windowing
+* Windows: Fix issues switching fullscreen while maximized (#1469)
+* macOS: Fix undecorated setting ignored when switching back to windowed mode
+* macOS: Fix window sizing bug when simultaneously changing undecorated
+* macOS: Fix black bar when switching to fullscreen with Z-Order set to Top
+* macOS: Squelch secure restorable state warning on macOS 14 "Sonoma"
+* X11: Fix crash on shutdown when using custom cursor
+* EGL: Fix invalid operation error when using headless pbuffer
+* EGL: Add support for resizing (offscreen) pbuffers
+* Windows: Message loop is no longer disabled when using tk with threaded draw
+* Windows: Fix handling of invalid raw mouse devices
+
+Deployment
+* Fix crash when running executable built with Python 3.11 (#1423)
+* Fix for thirdparty packages that use delvewheel (#1492)
+* Strip .abi3.so suffix from libraries
+* Silently ignore missing hidden imports
+* Add missing hidden imports for setuptools and shapely
+* Show better error message when targeting no-longer-supported platforms
+* Fix issues running pfreeze on macOS
+* Fix crash using on arm64 systems with 16 KiB pages (eg. Asahi Linux)
+* Fix error using bam_model_extensions with bdist_apps
+
+Pipeline
+* Support reading .bmp files with RLE8 compression
+* Fix wrong magfilter being written by egg-palettize (#1585)
+* Fix memory leaks when writing TIFF files
+* Fix hang in .egg loader when `<Collide>` used with `<Line>` (#1515)
+* Add assimp-disable-extensions config var (see #1537)
+* Fix assert/crash when reading multiple recorders from bam file (#1561)
+
+Build
+* Add support for building with Python 3.12
+* macOS .dmg are now built as HFS+ for 10.9 compatibility (#1502)
+* Don't use RTTI features in headers when building with `-fno-rtti`
+* Fix some new compiler warnings
+* Fix compile errors with some versions of png.h
+* Work around GCC 13.2 bug compiling interrogate-generated code (#1580)
+* Support building with LibreSSL on Windows (#1503)
+* Fix some issues in anticipation of Python 3.13+ (#1523, #1526)
+* More robust parsing of version in setup.cfg (incl. #1539)
+* Avoid using deprecated distutils module (#1549)
+* Add _d suffix to .pyd files when building debug build on Windows (#1566)
+
+Miscellaneous
+* Fix bug causing textures to sometimes be randomly downloaded
+* Fix PStats Python profiling exception when using metaclasses (#1505)
+* Fix segfault when ConnectionRepository is verbose (#1430)
+* Fix broken minusnode icon in dmodels (#1449)
+* Fix wrong delete operator in ARToolKit
+* Fix crash when encountering corruption in OpenAL stream (#1452)
+* Fix SimpleHashMap error reporting at static init time
+* Fix assorted code issues, typos, and Python 2-isms in direct tree
+* Code size reductions for interrogate-generated bindings
+* Assorted docstring corrections and additions
+* Interrogate supports explicit cls parameter for static methods
+* Interrogate: fix enum scope issue
+* Don't assume presence of softspace attribute on files in directnotify
+* Use importlib.metadata (not pkg_resources) for entry points in Python 3.8+
+
 -----------------------  RELEASE 1.10.13  -----------------------
 
 This is a significant release containing many important bug fixes and a couple

+ 296 - 246
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -1930,15 +1930,10 @@ write_module_class(ostream &out, Object *obj) {
             return_flags |= RF_preserve_null;
           }
           string expected_params;
-          write_function_forset(out, def._remaps, 0, 0, expected_params, 2, true, true,
-                                AT_no_args, return_flags, false);
-
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    return Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return nullptr;\n";
+          if (!write_function_forset(out, def._remaps, 0, 0, expected_params, 2, true, true,
+                                     AT_no_args, return_flags, false)) {
+            error_bad_args_return(out, 2, return_flags, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -1973,12 +1968,7 @@ write_module_class(ostream &out, Object *obj) {
           write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
                                 AT_single_arg, RF_err_null | RF_pyobject, false, !all_nonconst);
 
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    return Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return nullptr;\n";
+          error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
           out << "}\n\n";
         }
         break;
@@ -2087,40 +2077,31 @@ write_module_class(ostream &out, Object *obj) {
           if (!setattr_remaps.empty()) {
             out << "    PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
             string expected_params;
-            write_function_forset(out, setattr_remaps, 2, 2, expected_params, 4,
-                                  true, true, AT_varargs, RF_int | RF_decref_args, true);
-
-            out << "    Py_DECREF(args);\n";
-            out << "    if (!PyErr_Occurred()) {\n";
-            out << "      Dtool_Raise_BadArgumentsError(\n";
-            output_quoted(out, 8, expected_params);
-            out << ");\n";
-            out << "    }\n";
+            if (!write_function_forset(out, setattr_remaps, 2, 2, expected_params, 4,
+                                       true, true, AT_varargs, RF_int | RF_decref_args, true)) {
+              error_bad_args_return(out, 4, RF_int | RF_decref_args, expected_params);
+            }
           } else {
             out << "    PyErr_Format(PyExc_TypeError,\n";
             out << "      \"can't set attributes of built-in/extension type '%s'\",\n";
             out << "      Py_TYPE(self)->tp_name);\n";
+            out << "    return -1;\n\n";
           }
-          out << "    return -1;\n\n";
 
           out << "  } else { // __delattr__\n";
 
           if (!delattr_remaps.empty()) {
             string expected_params;
-            write_function_forset(out, delattr_remaps, 1, 1, expected_params, 4,
-                                  true, true, AT_single_arg, RF_int, true);
-
-            out << "    if (!PyErr_Occurred()) {\n";
-            out << "      Dtool_Raise_BadArgumentsError(\n";
-            output_quoted(out, 8, expected_params);
-            out << ");\n";
-            out << "    }\n";
+            if (!write_function_forset(out, delattr_remaps, 1, 1, expected_params, 4,
+                                       true, true, AT_single_arg, RF_int, true)) {
+              error_bad_args_return(out, 4, RF_int | RF_decref_args, expected_params);
+            }
           } else {
             out << "    PyErr_Format(PyExc_TypeError,\n";
             out << "      \"can't delete attributes of built-in/extension type '%s'\",\n";
             out << "      Py_TYPE(self)->tp_name);\n";
+            out << "    return -1;\n";
           }
-          out << "    return -1;\n";
           out << "  }\n";
 
           out << "}\n\n";
@@ -2153,12 +2134,12 @@ write_module_class(ostream &out, Object *obj) {
           out << "  }\n\n";
 
           string expected_params;
-          write_function_forset(out, def._remaps, 1, 1, expected_params, 2,
-                                true, true, AT_single_arg,
-                                RF_pyobject | RF_err_null, true);
-
-          // out << "  PyErr_Clear();\n";
-          out << "  return nullptr;\n";
+          if (!write_function_forset(out, def._remaps, 1, 1, expected_params, 2,
+                                     true, true, AT_single_arg,
+                                     RF_pyobject | RF_err_null, true)) {
+            // out << "  PyErr_Clear();\n";
+            out << "  return nullptr;\n";
+          }
           out << "}\n\n";
         }
         break;
@@ -2190,15 +2171,10 @@ write_module_class(ostream &out, Object *obj) {
           out << "  }\n";
 
           string expected_params;
-          write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
-                                AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
-
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    return Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return nullptr;\n";
+          if (!write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
+                                     AT_no_args, RF_pyobject | RF_err_null, false, true, "index")) {
+            error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -2240,20 +2216,22 @@ write_module_class(ostream &out, Object *obj) {
           }
 
           string expected_params;
+          bool always_returns = true;
           out << "  if (arg != nullptr) { // __setitem__\n";
-          write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
-                                true, true, AT_single_arg, RF_int, false, true, "index");
+          if (!write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
+                                     true, true, AT_single_arg, RF_int, false, true, "index")) {
+            always_returns = false;
+          }
           out << "  } else { // __delitem__\n";
-          write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
-                                true, true, AT_single_arg, RF_int, false, true, "index");
+          if (!write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
+                                     true, true, AT_single_arg, RF_int, false, true, "index")) {
+            always_returns = false;
+          }
           out << "  }\n\n";
 
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return -1;\n";
+          if (!always_returns) {
+            error_bad_args_return(out, 2, RF_int, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -2306,22 +2284,28 @@ write_module_class(ostream &out, Object *obj) {
           }
 
           string expected_params;
+          bool always_returns = true;
           out << "  if (arg2 != nullptr) { // __setitem__\n";
-          out << "    PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
-          write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
-                                true, true, AT_varargs, RF_int | RF_decref_args, false);
-          out << "    Py_DECREF(args);\n";
+          if (setitem_remaps.empty()) {
+            always_returns = false;
+          } else {
+            out << "    PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
+            if (!write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
+                                       true, true, AT_varargs, RF_int | RF_decref_args, false)) {
+              always_returns = false;
+            }
+            out << "    Py_DECREF(args);\n";
+          }
           out << "  } else { // __delitem__\n";
-          write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
-                                true, true, AT_single_arg, RF_int, false);
+          if (!write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
+                                     true, true, AT_single_arg, RF_int, false)) {
+            always_returns = false;
+          }
           out << "  }\n\n";
 
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return -1;\n";
+          if (!always_returns) {
+            error_bad_args_return(out, 2, RF_int, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -2541,23 +2525,28 @@ write_module_class(ostream &out, Object *obj) {
           }
 
           string expected_params;
-
+          bool always_returns = true;
           out << "  if (arg2 != nullptr && arg2 != Py_None) {\n";
-          out << "    PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
-          write_function_forset(out, two_param_remaps, 2, 2, expected_params, 4,
-                                true, true, AT_varargs, return_flags | RF_decref_args, true);
-          out << "    Py_DECREF(args);\n";
+          if (two_param_remaps.empty()) {
+            always_returns = false;
+          } else {
+            out << "    PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
+            if (!write_function_forset(out, two_param_remaps, 2, 2, expected_params, 4,
+                                       true, true, AT_varargs, return_flags | RF_decref_args, true)) {
+              always_returns = false;
+            }
+            out << "    Py_DECREF(args);\n";
+          }
           out << "  } else {\n";
-          write_function_forset(out, one_param_remaps, 1, 1, expected_params, 4,
-                                true, true, AT_single_arg, return_flags, true);
+          if (!write_function_forset(out, one_param_remaps, 1, 1, expected_params, 4,
+                                     true, true, AT_single_arg, return_flags, true)) {
+            always_returns = false;
+          }
           out << "  }\n\n";
 
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    return Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return nullptr;\n";
+          if (!always_returns) {
+            error_bad_args_return(out, 2, return_flags, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -2608,15 +2597,10 @@ write_module_class(ostream &out, Object *obj) {
           out << "  }\n\n";
 
           string expected_params;
-          write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
-                                AT_single_arg, RF_compare, false, true);
-
-          out << "  if (!PyErr_Occurred()) {\n";
-          out << "    Dtool_Raise_BadArgumentsError(\n";
-          output_quoted(out, 6, expected_params);
-          out << ");\n";
-          out << "  }\n";
-          out << "  return -1;\n";
+          if (!write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
+                                     AT_single_arg, RF_compare, false, true)) {
+            error_bad_args_return(out, 2, RF_int, expected_params);
+          }
           out << "}\n\n";
         }
         break;
@@ -2782,10 +2766,10 @@ write_module_class(ostream &out, Object *obj) {
       out << "    {\n";
 
       string expected_params;
-      write_function_forset(out, remaps, 1, 1, expected_params, 6, true, false,
-                            AT_single_arg, RF_pyobject | RF_err_null, false);
-
-      out << "      break;\n";
+      if (!write_function_forset(out, remaps, 1, 1, expected_params, 6, true, false,
+                                 AT_single_arg, RF_pyobject | RF_err_null, false)) {
+        out << "      break;\n";
+      }
       out << "    }\n";
     }
 
@@ -2818,10 +2802,10 @@ write_module_class(ostream &out, Object *obj) {
           out << "    {\n";
 
           string expected_params;
-          write_function_forset(out, remaps, 1, 1, expected_params, 6, true, false,
-                                AT_single_arg, RF_pyobject | RF_invert_bool | RF_err_null, false);
-
-          out << "      break;\n";
+          if (!write_function_forset(out, remaps, 1, 1, expected_params, 6, true, false,
+                                     AT_single_arg, RF_pyobject | RF_invert_bool | RF_err_null, false)) {
+            out << "      break;\n";
+          }
           out << "    }\n";
         }
       }
@@ -3932,17 +3916,24 @@ write_function_for_name(ostream &out, Object *obj,
         }
       }
 
+      bool always_returns;
       if (strip_keyword_args) {
         // None of the remaps take any keyword arguments, so let's check that
         // we take none.  This saves some checks later on.
         indent(out, 4) << "if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {\n";
         if (min_args == 1 && min_args == 1) {
           indent(out, 4) << "  PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
-          write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
-                coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst);
+          always_returns = write_function_forset(out, mii->second, min_args,
+                                                 max_args, expected_params, 6,
+                                                 coercion_allowed, true,
+                                                 AT_single_arg, return_flags,
+                                                 true, !all_nonconst);
         } else {
-          write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
-                coercion_allowed, true, AT_varargs, return_flags, true, !all_nonconst);
+          always_returns = write_function_forset(out, mii->second, min_args,
+                                                 max_args, expected_params, 6,
+                                                 coercion_allowed, true,
+                                                 AT_varargs, return_flags,
+                                                 true, !all_nonconst);
         }
       } else if (min_args == 1 && max_args == 1 && args_type == AT_varargs) {
         // We already checked that the args tuple has only one argument, so
@@ -3950,16 +3941,23 @@ write_function_for_name(ostream &out, Object *obj,
         indent(out, 4) << "{\n";
         indent(out, 4) << "  PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
 
-        write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
-                      coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst);
+        always_returns = write_function_forset(out, mii->second, min_args,
+                                               max_args, expected_params, 6,
+                                               coercion_allowed, true,
+                                               AT_single_arg, return_flags,
+                                               true, !all_nonconst);
       } else {
         indent(out, 4) << "{\n";
-        write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
-                      coercion_allowed, true, args_type, return_flags, true, !all_nonconst);
+        always_returns = write_function_forset(out, mii->second, min_args,
+                                               max_args, expected_params, 6,
+                                               coercion_allowed, true, args_type,
+                                               return_flags, true, !all_nonconst);
       }
 
       indent(out, 4) << "}\n";
-      indent(out, 4) << "break;\n";
+      if (!always_returns) {
+        indent(out, 4) << "break;\n";
+      }
     }
 
     // In NDEBUG case, fall through to the error at end of function.
@@ -3999,17 +3997,7 @@ write_function_for_name(ostream &out, Object *obj,
     out << "#endif\n";
     indent(out, 2) << "}\n";
 
-    out << "  if (!PyErr_Occurred()) {\n"
-        << "    ";
-    if ((return_flags & ~RF_pyobject) == RF_err_null) {
-      out << "return ";
-    }
-    out << "Dtool_Raise_BadArgumentsError(\n";
-    output_quoted(out, 6, expected_params);
-    out << ");\n"
-        << "  }\n";
-
-    error_return(out, 2, return_flags);
+    error_bad_args_return(out, 2, return_flags, expected_params);
 
   } else {
     mii = map_sets.begin();
@@ -4070,24 +4058,10 @@ write_function_for_name(ostream &out, Object *obj,
     }
 
     int min_args = min(max_required_args, mii->first);
-    write_function_forset(out, mii->second, min_args, mii->first, expected_params, 2,
-                          coercion_allowed, true, args_type, return_flags, true, !all_nonconst);
-
-    // This block is often unreachable for many functions... maybe we can
-    // figure out a way in the future to better determine when it will be and
-    // won't be necessary to write this out.
-    if (args_type != AT_no_args) {
-      out << "  if (!PyErr_Occurred()) {\n"
-          << "    ";
-      if ((return_flags & ~RF_pyobject) == RF_err_null) {
-        out << "return ";
-      }
-      out << "Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n"
-          << "  }\n";
-
-      error_return(out, 2, return_flags);
+    if (!write_function_forset(out, mii->second, min_args, mii->first,
+                               expected_params, 2, coercion_allowed, true,
+                               args_type, return_flags, true, !all_nonconst)) {
+      error_bad_args_return(out, 2, return_flags, expected_params);
     }
   }
 
@@ -4290,10 +4264,11 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
       }
       indent(out, 6) << "case " << max_args << ": {\n";
 
-      write_function_forset(out, mii->second, min_args, max_args, expected_params, 8, false, false,
-                            AT_varargs, return_flags, true, false);
-
-      indent(out, 8) << "break;\n";
+      if (!write_function_forset(out, mii->second, min_args, max_args,
+                                 expected_params, 8, false, false,
+                                 AT_varargs, return_flags, true, false)) {
+        indent(out, 8) << "break;\n";
+      }
       indent(out, 6) << "}\n";
     }
     indent(out, 4) << "}\n";
@@ -4540,7 +4515,7 @@ bool RemapCompareLess(FunctionRemap *in1, FunctionRemap *in2) {
  * first parameter.  This is a special-case hack for one of the slot
  * functions.
  */
-void InterfaceMakerPythonNative::
+bool InterfaceMakerPythonNative::
 write_function_forset(ostream &out,
                       const std::set<FunctionRemap *> &remapsin,
                       int min_num_args, int max_num_args,
@@ -4551,13 +4526,14 @@ write_function_forset(ostream &out,
                       const string &first_pexpr) {
 
   if (remapsin.empty()) {
-    return;
+    return false;
   }
 
   FunctionRemap *remap = nullptr;
   std::set<FunctionRemap *>::iterator sii;
 
   bool all_nonconst = false;
+  bool always_returns = true;
 
   if (verify_const) {
     // Check if all of the remaps are non-const.  If so, we only have to check
@@ -4613,6 +4589,7 @@ write_function_forset(ostream &out,
     indent(out, indent_level) << "if (Dtool_ExtractArg(&arg, args, kwds, \"" << first_param_name << "\")) {\n";
     indent_level += 2;
     args_type = AT_single_arg;
+    always_returns = false;
   }
 
   if (remapsin.size() > 1) {
@@ -4624,6 +4601,7 @@ write_function_forset(ostream &out,
     std::vector<FunctionRemap *>::const_iterator sii;
 
     int num_coercion_possible = 0;
+    bool caught_all = false;
     sii = remaps.begin();
     while (sii != remaps.end()) {
       remap = *(sii++);
@@ -4638,7 +4616,16 @@ write_function_forset(ostream &out,
         }
       }
 
-      if (verify_const && (remap->_has_this && !remap->_const_method)) {
+      if (caught_all) {
+        indent(out, indent_level)
+          << "  // [DCE] -2 \n";
+        remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
+        out << "\n";
+        continue;
+      }
+
+      bool remap_verify_const = verify_const && (remap->_has_this && !remap->_const_method);
+      if (remap_verify_const) {
         // If it's a non-const method, we only allow a non-const this.
         indent(out, indent_level)
           << "if (!DtoolInstance_IS_CONST(self)) {\n";
@@ -4654,17 +4641,23 @@ write_function_forset(ostream &out,
       // NB.  We don't pass on report_errors here because we want it to
       // silently drop down to the next overload.
 
-      write_function_instance(out, remap, min_num_args, max_num_args,
-                              expected_params, indent_level + 2,
-                              false, false, args_type, return_flags,
-                              check_exceptions, first_pexpr);
+      if (write_function_instance(out, remap, min_num_args, max_num_args,
+                                  expected_params, indent_level + 2,
+                                  false, false, args_type, return_flags,
+                                  check_exceptions, first_pexpr)) {
+        // The rest of the overloads are dead code.
+        if (!remap_verify_const) {
+          caught_all = true;
+          //indent(out, indent_level) << "  // caught all cases here\n";
+        }
+      }
 
       indent(out, indent_level) << "}\n\n";
     }
 
     // Go through one more time, but allow coercion this time.
-    if (coercion_allowed) {
-      for (sii = remaps.begin(); sii != remaps.end(); sii ++) {
+    if (coercion_allowed && !caught_all) {
+      for (sii = remaps.begin(); sii != remaps.end(); ++sii) {
         remap = (*sii);
         if (!is_remap_coercion_possible(remap)) {
           indent(out, indent_level)
@@ -4674,7 +4667,16 @@ write_function_forset(ostream &out,
           continue;
         }
 
-        if (verify_const && (remap->_has_this && !remap->_const_method)) {
+        if (caught_all) {
+          indent(out, indent_level)
+            << "  // [DCE] -2 \n";
+          remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
+          out << "\n";
+          continue;
+        }
+
+        bool remap_verify_const = verify_const && (remap->_has_this && !remap->_const_method);
+        if (remap_verify_const) {
           indent(out, indent_level)
             << "if (!DtoolInstance_IS_CONST(self)) {\n";
         } else {
@@ -4687,14 +4689,24 @@ write_function_forset(ostream &out,
         out << "\n";
 
         string ignore_expected_params;
-        write_function_instance(out, remap, min_num_args, max_num_args,
-                                ignore_expected_params, indent_level + 2,
-                                true, false, args_type, return_flags,
-                                check_exceptions, first_pexpr);
+        if (write_function_instance(out, remap, min_num_args, max_num_args,
+                                     ignore_expected_params, indent_level + 2,
+                                     true, false, args_type, return_flags,
+                                     check_exceptions, first_pexpr)) {
+          // The rest of the overloads are dead code.
+          if (!remap_verify_const) {
+            caught_all = true;
+            //indent(out, indent_level) << "  // caught all cases here\n";
+          }
+        }
 
         indent(out, indent_level) << "}\n\n";
       }
     }
+
+    if (!caught_all) {
+      always_returns = false;
+    }
   } else {
     // There is only one possible overload with this number of parameters.
     // Just call it.
@@ -4706,11 +4718,13 @@ write_function_forset(ostream &out,
     remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
     out << "\n";
 
-    write_function_instance(out, remap, min_num_args, max_num_args,
-                            expected_params, indent_level,
-                            coercion_allowed, report_errors,
-                            args_type, return_flags,
-                            check_exceptions, first_pexpr);
+    if (!write_function_instance(out, remap, min_num_args, max_num_args,
+                                 expected_params, indent_level,
+                                 coercion_allowed, report_errors,
+                                 args_type, return_flags,
+                                 check_exceptions, first_pexpr)) {
+      always_returns = false;
+    }
   }
 
   // Close the brace we opened earlier.
@@ -4739,10 +4753,14 @@ write_function_forset(ostream &out,
       out << "#else\n";
       error_raise_return(out, indent_level, return_flags, "TypeError", msg.str());
       out << "#endif\n";
+    } else {
+      always_returns = false;
     }
     indent_level -= 2;
     indent(out, indent_level) << "}\n";
   }
+
+  return always_returns;
 }
 
 /**
@@ -4769,8 +4787,11 @@ write_function_forset(ostream &out,
  * If first_pexpr is not empty, it represents the preconverted value of the
  * first parameter.  This is a special-case hack for one of the slot
  * functions.
+ *
+ * Returns true if the function returns unconditionally, false if it may fall
+ * through and additional error handling code is needed.
  */
-void InterfaceMakerPythonNative::
+bool InterfaceMakerPythonNative::
 write_function_instance(ostream &out, FunctionRemap *remap,
                         int min_num_args, int max_num_args,
                         string &expected_params, int indent_level,
@@ -4827,7 +4848,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
         --max_num_args;
       }
     }
-    nassertv(num_params <= (int)remap->_parameters.size());
+    nassertr(num_params <= (int)remap->_parameters.size(), false);
   }
 
   bool only_pyobjects = true;
@@ -4938,7 +4959,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
 
       // We should only ever have to consider optional arguments for functions
       // taking a variable number of arguments.
-      nassertv(args_type == AT_varargs || args_type == AT_keyword_args);
+      nassertr(args_type == AT_varargs || args_type == AT_keyword_args, false);
     }
 
     string reported_name = remap->_parameters[pn]._name;
@@ -6220,9 +6241,15 @@ write_function_instance(ostream &out, FunctionRemap *remap,
     // The general case; an ordinary constructor or function.
     return_expr = remap->call_function(out, indent_level, true, container, pexprs);
 
-    if (return_flags & RF_self) {
-      // We won't be using the return value, anyway.
-      return_expr.clear();
+    if ((return_flags & RF_self) != 0) {
+      if (TypeManager::is_pointer_to_PyObject(remap->_return_type->get_orig_type())) {
+        // If the function returns a PyObject *, let it override the default
+        // behavior of returning self.
+        return_flags = (return_flags & ~RF_self) | RF_pyobject;
+      } else {
+        // We won't be using the return value, anyway.
+        return_expr.clear();
+      }
     }
 
     if (!return_expr.empty()) {
@@ -6546,12 +6573,18 @@ write_function_instance(ostream &out, FunctionRemap *remap,
     indent(out, indent_level) << "}\n";
   }
 
-  // Close the extra braces opened earlier.
-  while (open_scopes > 0) {
-    indent_level -= 2;
-    indent(out, indent_level) << "}\n";
+  // If we were in a scope
+  bool always_returns = true;
+  if (open_scopes > 0) {
+    always_returns = false;
+
+    // Close the extra braces opened earlier.
+    while (open_scopes > 0) {
+      indent_level -= 2;
+      indent(out, indent_level) << "}\n";
 
-    --open_scopes;
+      --open_scopes;
+    }
   }
 
   if (clear_error && !report_errors) {
@@ -6563,7 +6596,10 @@ write_function_instance(ostream &out, FunctionRemap *remap,
   if (min_version > 0) {
     // Close the #if PY_VERSION_HEX check.
     out << "#endif\n";
+    always_returns = false;
   }
+
+  return always_returns;
 }
 
 /**
@@ -6594,6 +6630,55 @@ error_return(ostream &out, int indent_level, int return_flags) {
   }
 }
 
+/**
+ * Similar to error_return, except raises a "bad arguments" error before
+ * returning, if the error indicator has not yet been set.
+ */
+void InterfaceMakerPythonNative::
+error_bad_args_return(ostream &out, int indent_level, int return_flags,
+                      const string &expected_params) {
+
+  if (return_flags & RF_decref_args) {
+    indent(out, indent_level) << "Py_DECREF(args);\n";
+  }
+
+  if ((return_flags & RF_err_null) != 0 &&
+      (return_flags & RF_pyobject) != 0) {
+    // It always returns nullptr, so we'll allow the compiler to do a tail
+    // call optimization.
+    indent(out, indent_level) << "return Dtool_Raise_BadArgumentsError(\n";
+    output_quoted(out, indent_level + 2, expected_params);
+    out << ");\n";
+    return;
+  }
+
+  if (return_flags & RF_int) {
+    // Same, but for -1.
+    indent(out, indent_level) << "return Dtool_Raise_BadArgumentsError_Int(\n";
+    output_quoted(out, indent_level + 2, expected_params);
+    out << ");\n";
+    return;
+  }
+
+  indent(out, indent_level) << "Dtool_Raise_BadArgumentsError(\n";
+  output_quoted(out, indent_level + 2, expected_params);
+  out << ");\n";
+
+  if (return_flags & RF_int) {
+    indent(out, indent_level) << "return -1;\n";
+
+  } else if (return_flags & RF_err_notimplemented) {
+    indent(out, indent_level) << "Py_INCREF(Py_NotImplemented);\n";
+    indent(out, indent_level) << "return Py_NotImplemented;\n";
+
+  } else if (return_flags & RF_err_null) {
+    indent(out, indent_level) << "return nullptr;\n";
+
+  } else if (return_flags & RF_err_false) {
+    indent(out, indent_level) << "return false;\n";
+  }
+}
+
 /**
  * Similar to error_return, except raises an exception before returning.  If
  * format_args are not the empty string, uses PyErr_Format instead of
@@ -6924,15 +7009,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
     }
 
     string expected_params;
-    write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
-                          AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
-
-    out << "  if (!PyErr_Occurred()) {\n";
-    out << "    return Dtool_Raise_BadArgumentsError(\n";
-    output_quoted(out, 6, expected_params);
-    out << ");\n"
-            "  }\n"
-            "}\n\n";
+    if (!write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                               AT_no_args, RF_pyobject | RF_err_null, false, true, "index")) {
+      error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
+    }
+    out << "}\n\n";
 
     // Write out a setitem if this is not a read-only property.
     if (!property->_setter_remaps.empty()) {
@@ -6997,16 +7078,10 @@ write_getset(ostream &out, Object *obj, Property *property) {
       }
 
       string expected_params;
-      write_function_forset(out, remaps, 2, 2,
-                            expected_params, 2, true, true, AT_single_arg,
-                            RF_int, false, false, "index");
-
-      out << "  if (!PyErr_Occurred()) {\n";
-      out << "    Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n";
-      out << "  }\n";
-      out << "  return -1;\n";
+      if (!write_function_forset(out, remaps, 2, 2, expected_params, 2, true,
+                                 true, AT_single_arg, RF_int, false, false, "index")) {
+        error_bad_args_return(out, 2, RF_int, expected_params);
+      }
       out << "}\n\n";
     }
 
@@ -7026,16 +7101,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
                     property->_inserter->_remaps.end());
 
       string expected_params;
-      write_function_forset(out, remaps, 2, 2,
-                            expected_params, 2, true, true, AT_single_arg,
-                            RF_pyobject | RF_err_null, false, false, "index");
-
-      out << "  if (!PyErr_Occurred()) {\n";
-      out << "    Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n";
-      out << "  }\n";
-      out << "  return nullptr;\n";
+      if (!write_function_forset(out, remaps, 2, 2, expected_params, 2, true,
+                                 true, AT_single_arg, RF_pyobject | RF_err_null,
+                                 false, false, "index")) {
+        error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
+      }
       out << "}\n\n";
     }
   }
@@ -7095,16 +7165,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
     }
 
     string expected_params;
-    write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
-                          AT_single_arg, RF_pyobject | RF_err_null, false, true);
-
-    out << "  if (!PyErr_Occurred()) {\n";
-    out << "    return Dtool_Raise_BadArgumentsError(\n";
-    output_quoted(out, 6, expected_params);
-    out << ");\n"
-            "  }\n"
-            "  return nullptr;\n"
-            "}\n\n";
+    if (!write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                               AT_single_arg, RF_pyobject | RF_err_null, false, true)) {
+      error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
+    }
+    out << "}\n\n";
 
     // Write out a setitem if this is not a read-only property.
     if (!property->_setter_remaps.empty()) {
@@ -7144,10 +7209,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
                       property->_deleter->_remaps.end());
 
         string expected_params;
-        write_function_forset(out, remaps, 1, 1,
-                              expected_params, 4, true, true, AT_single_arg,
-                              RF_int, false, false);
-        out << "    return -1;\n";
+        if (!write_function_forset(out, remaps, 1, 1,
+                                   expected_params, 4, true, true, AT_single_arg,
+                                   RF_int, false, false)) {
+          out << "    return -1;\n";
+        }
       } else {
         out << "#ifdef NDEBUG\n"
                "    Dtool_Raise_TypeError(\"can't delete attribute\");\n"
@@ -7177,17 +7243,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
           << "  Py_INCREF(value);\n";
 
       string expected_params;
-      write_function_forset(out, remaps, 2, 2,
-                            expected_params, 2, true, true, AT_varargs,
-                            RF_int | RF_decref_args, false, false);
-
-      out << "  if (!PyErr_Occurred()) {\n";
-      out << "    Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n";
-      out << "  }\n";
-      out << "  Py_DECREF(args);\n";
-      out << "  return -1;\n";
+      if (!write_function_forset(out, remaps, 2, 2,
+                                 expected_params, 2, true, true, AT_varargs,
+                                 RF_int | RF_decref_args, false, false)) {
+        error_bad_args_return(out, 2, RF_int | RF_decref_args, expected_params);
+      }
       out << "}\n\n";
     }
 
@@ -7232,15 +7292,11 @@ write_getset(ostream &out, Object *obj, Property *property) {
       }
 
       string expected_params;
-      write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
-                            AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
-
-      out << "  if (!PyErr_Occurred()) {\n";
-      out << "    return Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n"
-              "  }\n"
-              "}\n\n";
+      if (!write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                                 AT_no_args, RF_pyobject | RF_err_null, false, true, "index")) {
+        error_bad_args_return(out, 2, RF_pyobject | RF_err_null, expected_params);
+      }
+      out << "}\n\n";
     }
   }
 
@@ -7432,16 +7488,10 @@ write_getset(ostream &out, Object *obj, Property *property) {
       }
 
       string expected_params;
-      write_function_forset(out, remaps, 1, 1,
-                            expected_params, 2, true, true, AT_single_arg,
-                            RF_int, false, false);
-
-      out << "  if (!PyErr_Occurred()) {\n";
-      out << "    Dtool_Raise_BadArgumentsError(\n";
-      output_quoted(out, 6, expected_params);
-      out << ");\n";
-      out << "  }\n";
-      out << "  return -1;\n";
+      if (!write_function_forset(out, remaps, 1, 1, expected_params, 2, true,
+                                 true, AT_single_arg, RF_int, false, false)) {
+        error_bad_args_return(out, 2, RF_int, expected_params);
+      }
       out << "}\n\n";
     }
   }

+ 4 - 2
dtool/src/interrogate/interfaceMakerPythonNative.h

@@ -155,7 +155,7 @@ private:
   int collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
                               int max_required_args);
 
-  void write_function_forset(std::ostream &out,
+  bool write_function_forset(std::ostream &out,
                              const std::set<FunctionRemap*> &remaps,
                              int min_num_args, int max_num_args,
                              std::string &expected_params, int indent_level,
@@ -165,7 +165,7 @@ private:
                              bool verify_const = true,
                              const std::string &first_expr = std::string());
 
-  void write_function_instance(std::ostream &out, FunctionRemap *remap,
+  bool write_function_instance(std::ostream &out, FunctionRemap *remap,
                                int min_num_args, int max_num_args,
                                std::string &expected_params, int indent_level,
                                bool coercion_allowed, bool report_errors,
@@ -174,6 +174,8 @@ private:
                                const std::string &first_pexpr = std::string());
 
   void error_return(std::ostream &out, int indent_level, int return_flags);
+  void error_bad_args_return(std::ostream &out, int indent_level, int return_flags,
+                             const std::string &expected_params);
   void error_raise_return(std::ostream &out, int indent_level, int return_flags,
                           const std::string &exc_type, const std::string &message,
                           const std::string &format_args = "");

+ 36 - 1
dtool/src/interrogatedb/py_panda.cxx

@@ -218,11 +218,46 @@ PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute) {
  * prints out a generic message, to help reduce the amount of strings in the
  * compiled library.
  *
+ * If there is already an exception set, does nothing.
+ *
  * Always returns NULL so that it can be conveniently used as a return
  * expression for wrapper functions that return a PyObject pointer.
  */
+PyObject *_Dtool_Raise_BadArgumentsError(const char *message) {
+  if (!PyErr_Occurred()) {
+    return PyErr_Format(PyExc_TypeError, "Arguments must match:\n%s", message);
+  }
+  return nullptr;
+}
+
+/**
+ * Overload that prints a generic message instead.
+ */
 PyObject *_Dtool_Raise_BadArgumentsError() {
-  return Dtool_Raise_TypeError("arguments do not match any function overload");
+  if (!PyErr_Occurred()) {
+    PyErr_SetString(PyExc_TypeError, "arguments do not match any function overload");
+  }
+  return nullptr;
+}
+
+/**
+ * Overload that returns -1 instead of nullptr.
+ */
+int _Dtool_Raise_BadArgumentsError_Int(const char *message) {
+  if (!PyErr_Occurred()) {
+    PyErr_Format(PyExc_TypeError, "Arguments must match:\n%s", message);
+  }
+  return -1;
+}
+
+/**
+ * Overload that returns -1 instead of nullptr and prints a generic message.
+ */
+int _Dtool_Raise_BadArgumentsError_Int() {
+  if (!PyErr_Occurred()) {
+    PyErr_SetString(PyExc_TypeError, "arguments do not match any function overload");
+  }
+  return -1;
 }
 
 /**

+ 6 - 1
dtool/src/interrogatedb/py_panda.h

@@ -216,12 +216,17 @@ EXPCL_PYPANDA PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const
 EXPCL_PYPANDA PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute);
 
 EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError();
+EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError(const char *message);
+EXPCL_PYPANDA int _Dtool_Raise_BadArgumentsError_Int();
+EXPCL_PYPANDA int _Dtool_Raise_BadArgumentsError_Int(const char *message);
 #ifdef NDEBUG
 // Define it to a function that just prints a generic message.
 #define Dtool_Raise_BadArgumentsError(x) _Dtool_Raise_BadArgumentsError()
+#define Dtool_Raise_BadArgumentsError_Int(x) _Dtool_Raise_BadArgumentsError_Int()
 #else
 // Expand this to a TypeError listing all of the overloads.
-#define Dtool_Raise_BadArgumentsError(x) Dtool_Raise_TypeError("Arguments must match:\n" x)
+#define Dtool_Raise_BadArgumentsError(x) _Dtool_Raise_BadArgumentsError(x)
+#define Dtool_Raise_BadArgumentsError_Int(x) _Dtool_Raise_BadArgumentsError_Int(x)
 #endif
 
 // These functions are similar to Dtool_WrapValue, except that they also

+ 12 - 0
makepanda/installer.nsi

@@ -382,6 +382,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
         !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
         !insertmacro PyBindingSection 3.12-32 .cp312-win32.pyd
+        !insertmacro PyBindingSection 3.13-32 .cp313-win32.pyd
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
@@ -391,6 +392,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
         !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
         !insertmacro PyBindingSection 3.12 .cp312-win_amd64.pyd
+        !insertmacro PyBindingSection 3.13 .cp313-win_amd64.pyd
     !endif
 SectionGroupEnd
 
@@ -501,6 +503,7 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.10-32
         !insertmacro MaybeEnablePyBindingSection 3.11-32
         !insertmacro MaybeEnablePyBindingSection 3.12-32
+        !insertmacro MaybeEnablePyBindingSection 3.13-32
         ${EndIf}
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
@@ -512,6 +515,7 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.10
         !insertmacro MaybeEnablePyBindingSection 3.11
         !insertmacro MaybeEnablePyBindingSection 3.12
+        !insertmacro MaybeEnablePyBindingSection 3.13
         ${EndIf}
     !endif
 
@@ -533,6 +537,10 @@ Function .onInit
         SectionSetFlags ${SecPyBindings3.12} ${SF_RO}
         SectionSetInstTypes ${SecPyBindings3.12} 0
     !endif
+    !ifdef SecPyBindings3.13
+        SectionSetFlags ${SecPyBindings3.13} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.13} 0
+    !endif
     ${EndUnless}
 FunctionEnd
 
@@ -851,6 +859,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.10-32
         !insertmacro RemovePythonPath 3.11-32
         !insertmacro RemovePythonPath 3.12-32
+        !insertmacro RemovePythonPath 3.13-32
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
@@ -860,6 +869,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.10
         !insertmacro RemovePythonPath 3.11
         !insertmacro RemovePythonPath 3.12
+        !insertmacro RemovePythonPath 3.13
     !endif
 
     SetDetailsPrint both
@@ -930,6 +940,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12-32} $(DESC_SecPyBindings3.12-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.13-32} $(DESC_SecPyBindings3.13-32)
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
@@ -939,6 +950,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12} $(DESC_SecPyBindings3.12)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.13} $(DESC_SecPyBindings3.13)
   !endif
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 7 - 2
makepanda/makepandacore.py

@@ -3408,10 +3408,15 @@ def SetOrigExt(x, v):
 def GetExtensionSuffix():
     target = GetTarget()
     if target == 'windows':
+        if GetOptimize() <= 2:
+            dllext = '_d'
+        else:
+            dllext = ''
+
         if GetTargetArch() == 'x64':
-            return '.cp%d%d-win_amd64.pyd' % (sys.version_info[:2])
+            return dllext + '.cp%d%d-win_amd64.pyd' % (sys.version_info[:2])
         else:
-            return '.cp%d%d-win32.pyd' % (sys.version_info[:2])
+            return dllext + '.cp%d%d-win32.pyd' % (sys.version_info[:2])
     elif target == 'emscripten':
         return '.so'
     elif CrossCompiling():

+ 5 - 0
panda/src/bullet/bulletContactCallbacks.h

@@ -88,7 +88,12 @@ contact_added_callback(btManifoldPoint &cp,
       BulletManifoldPoint mp(cp);
       BulletContactCallbackData cbdata(mp, node0, node1, id0, id1, index0, index1);
 
+      // Release the world mutex object so that bullet methods can be called from the callback.
+      LightMutex &mutex = BulletWorld::get_global_lock();
+
+      mutex.release();
       bullet_contact_added_callback->do_callback(&cbdata);
+      mutex.acquire();
     }
   }
 

+ 5 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -449,6 +449,11 @@ open_window() {
     _parent_window_handle->attach_child(_window_handle);
   }
 
+  if (_fb_properties.get_float_color() &&
+      [_view respondsToSelector:@selector(setWantsExtendedDynamicRangeOpenGLSurface:)]) {
+    [_view setWantsExtendedDynamicRangeOpenGLSurface:YES];
+  }
+
   // Configure the view to be high resolution capable using the dpi-aware
   // configuration flag. If dpi-aware is false, macOS will upscale the view
   // for us.

+ 4 - 0
panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.mm

@@ -188,6 +188,10 @@ choose_pixel_format(const FrameBufferProperties &properties,
   attribs.push_back(NSOpenGLPFAColorSize);
   attribs.push_back(properties.get_color_bits());
 
+  if (properties.get_float_color()) {
+    attribs.push_back(NSOpenGLPFAColorFloat);
+  }
+
   // Set the depth buffer bits to 24 manually when 1 is requested.
   // This prevents getting a depth buffer of only 16 bits when requesting 1.
   attribs.push_back(NSOpenGLPFADepthSize);

+ 1 - 1
panda/src/device/inputDeviceNode.cxx

@@ -69,7 +69,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
       const ButtonEvent &event = bel->get_event(i);
       if (event._type == ButtonEvent::T_down) {
         _button_states[event._button] = true;
-      } else if (event._type == ButtonEvent::T_down) {
+      } else if (event._type == ButtonEvent::T_up) {
         _button_states[event._button] = false;
       }
     }

+ 20 - 3
panda/src/display/graphicsPipeSelection.cxx

@@ -49,6 +49,7 @@ GraphicsPipeSelection() : _lock("GraphicsPipeSelection") {
 
   _default_display_module = load_display.get_word(0);
   _default_pipe_name = load_display.get_word(1);
+  _default_pipe_type = TypeHandle::none();
 
   if (_default_display_module == "*") {
     // '*' or empty string is the key for all display modules.
@@ -124,7 +125,11 @@ print_pipe_types() const {
   LightMutexHolder holder(_lock);
   nout << "Known pipe types:" << std::endl;
   for (const PipeType &pipe_type : _pipe_types) {
-    nout << "  " << pipe_type._type << "\n";
+    nout << "  " << pipe_type._type;
+    if (_pipe_types.size() > 1 && pipe_type._type == _default_pipe_type) {
+      nout << " (default)";
+    }
+    nout << "\n";
   }
   if (_display_modules.empty()) {
     nout << "(all display modules loaded.)\n";
@@ -256,7 +261,7 @@ make_default_pipe() {
 
   if (!_default_pipe_name.empty()) {
     // First, look for an exact match of the default type name from the
-    // Configrc file (excepting case and hyphenunderscore).
+    // Config.prc file (excepting case and hyphen / underscore).
     for (const PipeType &ptype : _pipe_types) {
       if (cmp_nocase_uh(ptype._type.get_name(), _default_pipe_name) == 0) {
         // Here's an exact match.
@@ -281,6 +286,18 @@ make_default_pipe() {
     }
   }
 
+  // Look for the preferred type of the default display module.
+  if (_default_pipe_type != TypeHandle::none()) {
+    for (const PipeType &ptype : _pipe_types) {
+      if (ptype._type == _default_pipe_type) {
+        PT(GraphicsPipe) pipe = (*ptype._constructor)();
+        if (pipe != nullptr) {
+          return pipe;
+        }
+      }
+    }
+  }
+
   // Couldn't find a matching pipe type; choose the first one on the list.
   for (const PipeType &ptype : _pipe_types) {
     PT(GraphicsPipe) pipe = (*ptype._constructor)();
@@ -358,7 +375,7 @@ do_load_default_module() {
     return;
   }
 
-  load_named_module(_default_display_module);
+  _default_pipe_type = load_named_module(_default_display_module);
 
   DisplayModules::iterator di =
     std::find(_display_modules.begin(), _display_modules.end(),

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

@@ -87,6 +87,7 @@ private:
   DisplayModules _display_modules;
   std::string _default_display_module;
   std::string _default_pipe_name;
+  TypeHandle _default_pipe_type;
   bool _default_module_loaded;
 
   static GraphicsPipeSelection *_global_ptr;

+ 1 - 1
panda/src/express/multifile.I

@@ -68,7 +68,7 @@ get_timestamp() const {
 }
 
 /**
- * Changes the overall mudification timestamp of the multifile.  Note that this
+ * Changes the overall modification timestamp of the multifile.  Note that this
  * will be reset to the current time every time you modify a subfile.
  * Only set this if you know what you are doing!
  */

+ 5 - 3
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -3196,7 +3196,7 @@ reset() {
   _max_image_units = 0;
 #ifndef OPENGLES_1
 #ifdef OPENGLES
-  if (is_at_least_gl_version(3, 1)) {
+  if (is_at_least_gles_version(3, 1) && gl_immutable_texture_storage) {
 #else
   if (is_at_least_gl_version(4, 2) || has_extension("GL_ARB_shader_image_load_store")) {
 #endif
@@ -14099,7 +14099,8 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
         << "Attempt to modify texture with immutable storage, recreating texture.\n";
       gtc->reset_data(gtc->_target, num_views);
     }
-    else if (_supports_tex_storage && gl_immutable_texture_storage) {
+    else if (_supports_tex_storage && gl_immutable_texture_storage &&
+             texture_type != Texture::TT_buffer_texture) {
       gtc->_immutable = true;
     }
   }
@@ -14313,7 +14314,8 @@ upload_texture_image(CLP(TextureContext) *gtc, int view, bool needs_reload,
         GLCAT.debug()
           << "allocating storage for texture " << tex->get_name() << ", "
           << width << " x " << height << " x " << depth << ", mipmaps "
-          << num_levels << "\n";
+          << num_levels << ", internal_format = 0x" << std::hex
+          << internal_format << std::dec << "\n";
       }
 
       switch (texture_type) {

+ 41 - 9
panda/src/glstuff/glShaderContext_src.cxx

@@ -1192,7 +1192,7 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
       if (param_type == GL_FLOAT_VEC3) {
         bind._piece = Shader::SMP_vec3;
       } else if (param_type == GL_FLOAT_VEC4) {
-        bind._piece = Shader::SMP_vec3;
+        bind._piece = Shader::SMP_vec4;
       } else {
         GLCAT.error()
           << "p3d_Color should be vec3 or vec4\n";
@@ -1708,17 +1708,35 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
       case GL_UNSIGNED_INT_IMAGE_BUFFER:
         // This won't really change at runtime, so we might as well bind once
         // and then forget about it.
-        // Note that OpenGL ES doesn't support changing this at runtime, so we
-        // rely on the shader using a layout declaration.
-#ifndef OPENGLES
-        _glgsg->_glUniform1i(p, _glsl_img_inputs.size());
-#endif
         {
+#ifdef OPENGLES
+          // In OpenGL ES, we can't choose our own binding, but we can ask the
+          // driver what it assigned (or what the shader specified).
+          GLint binding = 0;
+          glGetUniformiv(_glsl_program, p, &binding);
+          if (GLCAT.is_debug()) {
+            GLCAT.debug()
+              << "Active uniform " << param_name
+              << " is bound to image unit " << binding << "\n";
+          }
+
+          if (binding >= _glsl_img_inputs.size()) {
+            _glsl_img_inputs.resize(binding + 1);
+          }
+
+          ImageInput &input = _glsl_img_inputs[binding];
+          input._name = InternalName::make(param_name);
+#else
+          if (GLCAT.is_debug()) {
+            GLCAT.debug()
+              << "Binding image uniform " << param_name
+              << " to image unit " << _glsl_img_inputs.size() << "\n";
+          }
+          _glgsg->_glUniform1i(p, _glsl_img_inputs.size());
           ImageInput input;
           input._name = InternalName::make(param_name);
-          input._writable = false;
-          input._gtc = nullptr;
-          _glsl_img_inputs.push_back(input);
+          _glsl_img_inputs.push_back(std::move(input));
+#endif
         }
         return;
       default:
@@ -2676,6 +2694,10 @@ update_shader_texture_bindings(ShaderContext *prev) {
       const ParamTextureImage *param = nullptr;
       Texture *tex;
 
+      if (input._name == nullptr) {
+        continue;
+      }
+
       const ShaderInput &sinp = _glgsg->_target_shader->get_shader_input(input._name);
       switch (sinp.get_value_type()) {
       case ShaderInput::M_texture_image:
@@ -2729,6 +2751,16 @@ update_shader_texture_bindings(ShaderContext *prev) {
         // TODO: automatically convert to sized type instead of plain GL_RGBA
         // If a base type is used, it will crash.
         GLenum internal_format = gtc->_internal_format;
+#ifdef OPENGLES
+        if (!gtc->_immutable) {
+          static bool error_shown = false;
+          if (!error_shown) {
+            error_shown = true;
+            GLCAT.error()
+              << "Enable gl-immutable-texture-storage to use image textures in OpenGL ES.\n";
+          }
+        }
+#endif
         if (internal_format == GL_RGBA || internal_format == GL_RGB) {
           GLCAT.error()
             << "Texture " << tex->get_name() << " has an unsized format.  Textures bound "

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

@@ -107,8 +107,8 @@ private:
 
   struct ImageInput {
     CPT(InternalName) _name;
-    CLP(TextureContext) *_gtc;
-    bool _writable;
+    CLP(TextureContext) *_gtc = nullptr;
+    bool _writable = false;
   };
   pvector<ImageInput> _glsl_img_inputs;
 

+ 4 - 4
panda/src/gobj/texture.I

@@ -1888,7 +1888,7 @@ set_filename(const Filename &filename) {
 }
 
 /**
- * Removes the alpha filename, if it was previously set.  See set_filename().
+ * Removes the filename, if it was previously set.  See set_filename().
  */
 INLINE void Texture::
 clear_filename() {
@@ -1904,8 +1904,8 @@ clear_filename() {
  * The Texture's get_filename() function returns the name of the image file
  * that was loaded into the buffer.  In the case where a texture specified two
  * separate files to load, a 1- or 3-channel color image and a 1-channel alpha
- * image, this Filename is update to contain the name of the image file that
- * was loaded into the buffer's alpha channel.
+ * image, the alpha_filename is updated to contain the name of the image file
+ * that was loaded into the buffer's alpha channel.
  */
 INLINE void Texture::
 set_alpha_filename(const Filename &alpha_filename) {
@@ -1935,7 +1935,7 @@ set_fullpath(const Filename &fullpath) {
 }
 
 /**
- * Removes the alpha fullpath, if it was previously set.  See set_fullpath().
+ * Removes the fullpath, if it was previously set.  See set_fullpath().
  */
 INLINE void Texture::
 clear_fullpath() {

+ 5 - 0
panda/src/grutil/meshDrawer.cxx

@@ -416,6 +416,11 @@ void MeshDrawer::geometry(NodePath draw_node) {
 /**
  * Stars or continues linked segment.  Control position, frame, thickness and
  * color with parameters.  Frame contains u,v,u-size,v-size quadruple.
+ * Note that for the first two calls to this method, the "frame" parameter is
+ * ignored; it first takes effect as of the third call.
+ * Similarly, note that in the second call to this method, the "color" parameter
+ * is ignored; it only has effect in the first call and calls from the third
+ * onwards.
  */
 void MeshDrawer::
 link_segment(const LVector3 &pos, const LVector4 &frame,

+ 2 - 1
panda/src/pgraph/lensNode.cxx

@@ -119,7 +119,8 @@ set_lens_active(int index, bool flag) {
 
 /**
  * Returns true if the given point is within the bounds of the lens of the
- * LensNode (i.e.  if the camera can see the point).
+ * LensNode (i.e.  if the camera can see the point).  The point is assumed to
+ * be relative to the LensNode itself.
  */
 bool LensNode::
 is_in_view(int index, const LPoint3 &pos) {

+ 19 - 0
panda/src/pgui/pgEntry.h

@@ -80,6 +80,7 @@ PUBLISHED:
 
   INLINE void set_cursor_position(int position);
   INLINE int get_cursor_position() const;
+  MAKE_PROPERTY(cursor_position, get_cursor_position, set_cursor_position);
 
   INLINE PN_stdfloat get_cursor_X() const;
   INLINE PN_stdfloat get_cursor_Y() const;
@@ -90,33 +91,45 @@ PUBLISHED:
   INLINE PN_stdfloat get_max_width() const;
   INLINE void set_num_lines(int num_lines);
   INLINE int get_num_lines() const;
+  MAKE_PROPERTY(max_chars, get_max_chars, set_max_chars);
+  MAKE_PROPERTY(max_width, get_max_width, set_max_width);
+  MAKE_PROPERTY(num_lines, get_num_lines, set_num_lines);
 
   INLINE void set_blink_rate(PN_stdfloat blink_rate);
   INLINE PN_stdfloat get_blink_rate() const;
+  MAKE_PROPERTY(blink_rate, get_blink_rate, set_blink_rate);
 
   INLINE NodePath get_cursor_def();
   INLINE void clear_cursor_def();
+  MAKE_PROPERTY(cursor_def, get_cursor_def);
 
   INLINE void set_cursor_keys_active(bool flag);
   INLINE bool get_cursor_keys_active() const;
+  MAKE_PROPERTY(cursor_keys_active, get_cursor_keys_active, set_cursor_keys_active);
 
   INLINE void set_obscure_mode(bool flag);
   INLINE bool get_obscure_mode() const;
+  MAKE_PROPERTY(obscure_mode, get_obscure_mode, set_obscure_mode);
 
   INLINE void set_overflow_mode(bool flag);
   INLINE bool get_overflow_mode() const;
+  MAKE_PROPERTY(overflow_mode, get_overflow_mode, set_overflow_mode);
 
   INLINE void set_candidate_active(const std::string &candidate_active);
   INLINE const std::string &get_candidate_active() const;
+  MAKE_PROPERTY(candidate_active, get_candidate_active, set_candidate_active);
 
   INLINE void set_candidate_inactive(const std::string &candidate_inactive);
   INLINE const std::string &get_candidate_inactive() const;
+  MAKE_PROPERTY(candidate_inactive, get_candidate_inactive, set_candidate_inactive);
 
   void set_text_def(int state, TextNode *node);
   TextNode *get_text_def(int state) const;
 
   virtual void set_active(bool active) final;
   virtual void set_focus(bool focus);
+  MAKE_PROPERTY(active, get_active, set_active);
+  MAKE_PROPERTY(focus, get_focus, set_focus);
 
   INLINE static std::string get_accept_prefix();
   INLINE static std::string get_accept_failed_prefix();
@@ -132,6 +145,12 @@ PUBLISHED:
   INLINE std::string get_erase_event() const;
   INLINE std::string get_cursormove_event() const;
 
+  MAKE_PROPERTY(accept_prefix, get_accept_prefix);
+  MAKE_PROPERTY(accept_failed_prefix, get_accept_failed_prefix);
+  MAKE_PROPERTY(overflow_prefix, get_overflow_prefix);
+  MAKE_PROPERTY(type_prefix, get_type_prefix);
+  MAKE_PROPERTY(erase_prefix, get_erase_prefix);
+  MAKE_PROPERTY(cursormove_prefix, get_cursormove_prefix);
 
   INLINE bool set_wtext(const std::wstring &wtext);
   INLINE std::wstring get_plain_wtext() const;

+ 6 - 0
panda/src/pgui/pgFrameStyle.h

@@ -47,27 +47,33 @@ PUBLISHED:
 
   INLINE void set_type(Type type);
   INLINE Type get_type() const;
+  MAKE_PROPERTY(type, get_type, set_type);
 
   INLINE void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a);
   INLINE void set_color(const LColor &color);
   INLINE LColor get_color() const;
+  MAKE_PROPERTY(color, get_color, set_color);
 
   INLINE void set_texture(Texture *texture);
   INLINE bool has_texture() const;
   INLINE Texture *get_texture() const;
   INLINE void clear_texture();
+  MAKE_PROPERTY2(texture, has_texture, get_texture, set_texture, clear_texture);
 
   INLINE void set_width(PN_stdfloat x, PN_stdfloat y);
   INLINE void set_width(const LVecBase2 &width);
   INLINE const LVecBase2 &get_width() const;
+  MAKE_PROPERTY(width, get_width, set_width);
 
   INLINE void set_uv_width(PN_stdfloat u, PN_stdfloat v);
   INLINE void set_uv_width(const LVecBase2 &uv_width);
   INLINE const LVecBase2 &get_uv_width() const;
+  MAKE_PROPERTY(uv_width, get_uv_width, set_uv_width);
 
   INLINE void set_visible_scale(PN_stdfloat x, PN_stdfloat y);
   INLINE void set_visible_scale(const LVecBase2 &visible_scale);
   INLINE const LVecBase2 &get_visible_scale() const;
+  MAKE_PROPERTY(visible_scale, get_visible_scale, set_visible_scale);
 
   LVecBase4 get_internal_frame(const LVecBase4 &frame) const;
 

+ 12 - 0
panda/src/pgui/pgItem.h

@@ -132,6 +132,7 @@ PUBLISHED:
   bool has_state_def(int state) const;
   INLINE NodePath &get_state_def(int state);
   MAKE_SEQ(get_state_defs, get_num_state_defs, get_state_def);
+  MAKE_SEQ_PROPERTY(state_defs, get_num_state_defs, get_state_def);
   NodePath instance_to_state_def(int state, const NodePath &path);
 
   PGFrameStyle get_frame_style(int state);
@@ -171,6 +172,17 @@ PUBLISHED:
   INLINE std::string get_release_event(const ButtonHandle &button) const;
   INLINE std::string get_keystroke_event() const;
 
+  MAKE_PROPERTY(enter_prefix, get_enter_prefix);
+  MAKE_PROPERTY(exit_prefix, get_exit_prefix);
+  MAKE_PROPERTY(within_prefix, get_within_prefix);
+  MAKE_PROPERTY(without_prefix, get_without_prefix);
+  MAKE_PROPERTY(focus_in_prefix, get_focus_in_prefix);
+  MAKE_PROPERTY(focus_out_prefix, get_focus_out_prefix);
+  MAKE_PROPERTY(press_prefix, get_press_prefix);
+  MAKE_PROPERTY(repeat_prefix, get_repeat_prefix);
+  MAKE_PROPERTY(release_prefix, get_release_prefix);
+  MAKE_PROPERTY(keystroke_prefix, get_keystroke_prefix);
+
   INLINE LMatrix4 get_frame_inv_xform() const;
 
 #ifdef HAVE_AUDIO

+ 11 - 0
panda/src/pgui/pgScrollFrame.h

@@ -56,20 +56,31 @@ PUBLISHED:
   INLINE const LVecBase4 &get_virtual_frame() const;
   INLINE bool has_virtual_frame() const;
   INLINE void clear_virtual_frame();
+  MAKE_PROPERTY2(virtual_frame,
+                 has_virtual_frame, get_virtual_frame,
+                 set_virtual_frame, clear_virtual_frame);
 
   INLINE void set_manage_pieces(bool manage_pieces);
   INLINE bool get_manage_pieces() const;
+  MAKE_PROPERTY(manage_pieces, get_manage_pieces, set_manage_pieces);
 
   INLINE void set_auto_hide(bool auto_hide);
   INLINE bool get_auto_hide() const;
+  MAKE_PROPERTY(auto_hide, get_auto_hide, set_auto_hide);
 
   INLINE void set_horizontal_slider(PGSliderBar *horizontal_slider);
   INLINE void clear_horizontal_slider();
   INLINE PGSliderBar *get_horizontal_slider() const;
+  MAKE_PROPERTY2(horizontal_slider,
+                 get_horizontal_slider, get_horizontal_slider,
+                 set_horizontal_slider, clear_horizontal_slider);
 
   INLINE void set_vertical_slider(PGSliderBar *vertical_slider);
   INLINE void clear_vertical_slider();
   INLINE PGSliderBar *get_vertical_slider() const;
+  MAKE_PROPERTY2(vertical_slider,
+                 get_vertical_slider, get_vertical_slider,
+                 set_vertical_slider, clear_vertical_slider);
 
   void remanage();
   INLINE void recompute();

+ 19 - 0
panda/src/pgui/pgSliderBar.h

@@ -56,6 +56,7 @@ PUBLISHED:
 
   INLINE void set_axis(const LVector3 &axis);
   INLINE const LVector3 &get_axis() const;
+  MAKE_PROPERTY(axis, get_axis, set_axis);
 
   INLINE void set_range(PN_stdfloat min_value, PN_stdfloat max_value);
   INLINE PN_stdfloat get_min_value() const;
@@ -63,40 +64,58 @@ PUBLISHED:
 
   INLINE void set_scroll_size(PN_stdfloat scroll_size);
   INLINE PN_stdfloat get_scroll_size() const;
+  MAKE_PROPERTY(scroll_size, get_scroll_size, set_scroll_size);
 
   INLINE void set_page_size(PN_stdfloat page_size);
   INLINE PN_stdfloat get_page_size() const;
+  MAKE_PROPERTY(page_size, get_page_size, set_page_size);
 
   INLINE void set_value(PN_stdfloat value);
   INLINE PN_stdfloat get_value() const;
+  MAKE_PROPERTY(value, get_value, set_value);
 
   INLINE void set_ratio(PN_stdfloat ratio);
   INLINE PN_stdfloat get_ratio() const;
+  MAKE_PROPERTY(ratio, get_ratio, set_ratio);
 
   INLINE bool is_button_down() const;
+  MAKE_PROPERTY(button_down, is_button_down);
 
   INLINE void set_resize_thumb(bool resize_thumb);
   INLINE bool get_resize_thumb() const;
+  MAKE_PROPERTY(resize_thumb, get_resize_thumb, set_resize_thumb);
 
   INLINE void set_manage_pieces(bool manage_pieces);
   INLINE bool get_manage_pieces() const;
+  MAKE_PROPERTY(manage_pieces, get_manage_pieces, set_manage_pieces);
 
   INLINE void set_thumb_button(PGButton *thumb_button);
   INLINE void clear_thumb_button();
   INLINE PGButton *get_thumb_button() const;
+  MAKE_PROPERTY2(thumb_button,
+                 get_thumb_button, get_thumb_button,
+                 set_thumb_button, clear_thumb_button);
 
   INLINE void set_left_button(PGButton *left_button);
   INLINE void clear_left_button();
   INLINE PGButton *get_left_button() const;
+  MAKE_PROPERTY2(left_button,
+                 get_left_button, get_left_button,
+                 set_left_button, clear_left_button);
 
   INLINE void set_right_button(PGButton *right_button);
   INLINE void clear_right_button();
   INLINE PGButton *get_right_button() const;
+  MAKE_PROPERTY2(right_button,
+                 get_right_button, get_right_button,
+                 set_right_button, clear_right_button);
 
   INLINE static std::string get_adjust_prefix();
   INLINE std::string get_adjust_event() const;
+  MAKE_PROPERTY(adjust_prefix, get_adjust_prefix);
 
   virtual void set_active(bool active) final;
+  MAKE_PROPERTY(active, get_active, set_active);
 
   void remanage();
   void recompute();

+ 3 - 0
panda/src/pgui/pgTop.h

@@ -51,9 +51,12 @@ PUBLISHED:
   void set_mouse_watcher(MouseWatcher *watcher);
   INLINE MouseWatcher *get_mouse_watcher() const;
   INLINE MouseWatcherGroup *get_group() const;
+  MAKE_PROPERTY(mouse_watcher, get_mouse_watcher, set_mouse_watcher);
+  MAKE_PROPERTY(group, get_group);
 
   INLINE void set_start_sort(int start_sort);
   INLINE int get_start_sort() const;
+  MAKE_PROPERTY(start_sort, get_start_sort, set_start_sort);
 
 public:
   // These methods duplicate the functionality of MouseWatcherGroup.

+ 4 - 0
panda/src/pgui/pgVirtualFrame.h

@@ -59,12 +59,16 @@ PUBLISHED:
   INLINE const LVecBase4 &get_clip_frame() const;
   INLINE bool has_clip_frame() const;
   void clear_clip_frame();
+  MAKE_PROPERTY2(clip_frame, has_clip_frame, get_clip_frame, set_clip_frame, clear_clip_frame);
 
   INLINE void set_canvas_transform(const TransformState *transform);
   INLINE const TransformState *get_canvas_transform() const;
+  MAKE_PROPERTY(canvas_transform, get_canvas_transform, set_canvas_transform);
 
   INLINE PandaNode *get_canvas_node() const;
   INLINE PandaNode *get_canvas_parent() const;
+  MAKE_PROPERTY(canvas_node, get_canvas_node);
+  MAKE_PROPERTY(canvas_parent, get_canvas_parent);
 
 protected:
   virtual void clip_frame_changed();

+ 3 - 0
panda/src/pgui/pgWaitBar.h

@@ -40,14 +40,17 @@ PUBLISHED:
 
   INLINE void set_range(PN_stdfloat range);
   INLINE PN_stdfloat get_range() const;
+  MAKE_PROPERTY(range, get_range, set_range);
 
   INLINE void set_value(PN_stdfloat value);
   INLINE PN_stdfloat get_value() const;
+  MAKE_PROPERTY(value, get_value, set_value);
 
   INLINE PN_stdfloat get_percent() const;
 
   INLINE void set_bar_style(const PGFrameStyle &style);
   INLINE PGFrameStyle get_bar_style() const;
+  MAKE_PROPERTY(bar_style, get_bar_style, set_bar_style);
 
 private:
   void update();

+ 9 - 6
panda/src/pipeline/thread.cxx

@@ -92,16 +92,19 @@ Thread::
  */
 PT(Thread) Thread::
 bind_thread(const std::string &name, const std::string &sync_name) {
-  Thread *current_thread = get_current_thread();
-  if (current_thread != get_external_thread()) {
+  PT(Thread) thread = new ExternalThread(name, sync_name);
+#ifndef HAVE_THREADS
+  Thread *current_thread = get_main_thread();
+#else
+  Thread *current_thread = ThreadImpl::bind_thread(thread);
+#endif
+  if (current_thread != nullptr &&
+      current_thread != thread.p()) {
     // This thread already has an associated thread.
     nassertr(current_thread->get_name() == name &&
              current_thread->get_sync_name() == sync_name, current_thread);
-    return current_thread;
+    thread = current_thread;
   }
-
-  PT(Thread) thread = new ExternalThread(name, sync_name);
-  ThreadImpl::bind_thread(thread);
   return thread;
 }
 

+ 5 - 3
panda/src/pipeline/threadDummyImpl.I

@@ -63,13 +63,15 @@ prepare_for_exit() {
 }
 
 /**
- * Associates the indicated Thread object with the currently-executing thread.
+ * Associates the indicated Thread object with the currently-executing thread,
+ * unless a thread is already bound, in which case it is returned.
  * You should not call this directly; use Thread::bind_thread() instead.
  */
-INLINE void ThreadDummyImpl::
+INLINE Thread *ThreadDummyImpl::
 bind_thread(Thread *thread) {
   // This method shouldn't be called in the non-threaded case.
-  nassertv(false);
+  nassertr(false, nullptr);
+  return nullptr;
 }
 
 /**

+ 1 - 1
panda/src/pipeline/threadDummyImpl.h

@@ -55,7 +55,7 @@ public:
   INLINE static void prepare_for_exit();
 
   static Thread *get_current_thread();
-  INLINE static void bind_thread(Thread *thread);
+  INLINE static Thread *bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   INLINE static bool is_true_threads();
   INLINE static bool is_simple_threads();

+ 8 - 3
panda/src/pipeline/threadPosixImpl.cxx

@@ -193,18 +193,23 @@ get_unique_id() const {
 }
 
 /**
- * Associates the indicated Thread object with the currently-executing thread.
+ * Associates the indicated Thread object with the currently-executing thread,
+ * unless a thread is already bound, in which case it is returned.
  * You should not call this directly; use Thread::bind_thread() instead.
  */
-void ThreadPosixImpl::
+Thread *ThreadPosixImpl::
 bind_thread(Thread *thread) {
-  if (_current_thread == nullptr && thread == Thread::get_main_thread()) {
+  if (_current_thread != nullptr) {
+    return _current_thread;
+  }
+  if (thread == Thread::get_main_thread()) {
     _main_thread_known.test_and_set(std::memory_order_relaxed);
   }
   _current_thread = thread;
 #ifdef ANDROID
   bind_java_thread();
 #endif
+  return thread;
 }
 
 #ifdef ANDROID

+ 1 - 1
panda/src/pipeline/threadPosixImpl.h

@@ -49,7 +49,7 @@ public:
   INLINE static void prepare_for_exit();
 
   INLINE static Thread *get_current_thread();
-  static void bind_thread(Thread *thread);
+  static Thread *bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   INLINE static bool is_true_threads();
   INLINE static bool is_simple_threads();

+ 4 - 2
panda/src/pipeline/threadSimpleImpl.I

@@ -36,11 +36,13 @@ is_same_system_thread() const {
 }
 
 /**
- * Associates the indicated Thread object with the currently-executing thread.
+ * Associates the indicated Thread object with the currently-executing thread,
+ * unless a thread is already bound, in which case it is returned.
  * You should not call this directly; use Thread::bind_thread() instead.
  */
-INLINE void ThreadSimpleImpl::
+INLINE Thread *ThreadSimpleImpl::
 bind_thread(Thread *) {
+  return get_current_thread();
 }
 
 /**

+ 1 - 1
panda/src/pipeline/threadSimpleImpl.h

@@ -61,7 +61,7 @@ public:
   INLINE static Thread *get_current_thread();
   INLINE bool is_same_system_thread() const;
 
-  INLINE static void bind_thread(Thread *thread);
+  INLINE static Thread *bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   static bool is_true_threads();
   INLINE static bool is_simple_threads();

+ 8 - 3
panda/src/pipeline/threadWin32Impl.cxx

@@ -184,15 +184,20 @@ get_current_thread() {
 }
 
 /**
- * Associates the indicated Thread object with the currently-executing thread.
+ * Associates the indicated Thread object with the currently-executing thread,
+ * unless a thread is already bound, in which case it is returned.
  * You should not call this directly; use Thread::bind_thread() instead.
  */
-void ThreadWin32Impl::
+Thread *ThreadWin32Impl::
 bind_thread(Thread *thread) {
-  if (_current_thread == nullptr && thread == Thread::get_main_thread()) {
+  if (_current_thread != nullptr) {
+    return _current_thread;
+  }
+  if (thread == Thread::get_main_thread()) {
     _main_thread_known.test_and_set(std::memory_order_relaxed);
   }
   _current_thread = thread;
+  return thread;
 }
 
 /**

+ 1 - 1
panda/src/pipeline/threadWin32Impl.h

@@ -44,7 +44,7 @@ public:
   INLINE static void prepare_for_exit();
 
   static Thread *get_current_thread();
-  static void bind_thread(Thread *thread);
+  static Thread *bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   INLINE static bool is_true_threads();
   INLINE static bool is_simple_threads();

+ 2 - 2
panda/src/putil/bamReaderParam.I

@@ -14,7 +14,7 @@
 /**
  *
  */
-INLINE const DatagramIterator &BamReaderParam::
+INLINE DatagramIterator &BamReaderParam::
 get_iterator() {
   return _iterator;
 }
@@ -32,7 +32,7 @@ get_manager() {
  *
  */
 INLINE BamReaderParam::
-BamReaderParam(const DatagramIterator &dgi, BamReader *manager) :
+BamReaderParam(DatagramIterator &dgi, BamReader *manager) :
   _iterator(dgi),
   _manager(manager)
 {

+ 3 - 3
panda/src/putil/bamReaderParam.h

@@ -27,15 +27,15 @@ class DatagramIterator;
  */
 class EXPCL_PANDA_PUTIL BamReaderParam : public FactoryParam {
 public:
-  INLINE const DatagramIterator &get_iterator();
+  INLINE DatagramIterator &get_iterator();
   INLINE BamReader *get_manager();
 
 private:
-  const DatagramIterator &_iterator;
+  DatagramIterator &_iterator;
   BamReader *_manager;
 
 public:
-  INLINE BamReaderParam(const DatagramIterator &dgi, BamReader *manager);
+  INLINE BamReaderParam(DatagramIterator &dgi, BamReader *manager);
   INLINE ~BamReaderParam();
 
 public:

+ 2 - 4
panda/src/recorder/mouseRecorder.cxx

@@ -228,11 +228,9 @@ make_from_bam(const FactoryParams &params) {
 RecorderBase *MouseRecorder::
 make_recorder(const FactoryParams &params) {
   MouseRecorder *node = new MouseRecorder("");
-  DatagramIterator scan;
-  BamReader *manager;
+  BamReaderParam *param = DCAST(BamReaderParam, params.get_param(0));
 
-  parse_params(params, scan, manager);
-  node->fillin_recorder(scan, manager);
+  node->fillin_recorder(param->get_iterator(), param->get_manager());
 
   return node;
 }

+ 2 - 4
panda/src/recorder/socketStreamRecorder.cxx

@@ -113,11 +113,9 @@ write_recorder(BamWriter *manager, Datagram &dg) {
 RecorderBase *SocketStreamRecorder::
 make_recorder(const FactoryParams &params) {
   SocketStreamRecorder *node = new SocketStreamRecorder;
-  DatagramIterator scan;
-  BamReader *manager;
+  BamReaderParam *param = DCAST(BamReaderParam, params.get_param(0));
 
-  parse_params(params, scan, manager);
-  node->fillin_recorder(scan, manager);
+  node->fillin_recorder(param->get_iterator(), param->get_manager());
 
   return node;
 }

+ 4 - 0
panda/src/text/config_text.cxx

@@ -62,7 +62,11 @@ ConfigVariableBool text_kerning
           "is true, since HarfBuzz offers superior kerning support."));
 
 ConfigVariableBool text_use_harfbuzz
+#ifdef HAVE_HARFBUZZ
+("text-use-harfbuzz", true,
+#else
 ("text-use-harfbuzz", false,
+#endif
  PRC_DESC("Set this true to enable HarfBuzz support, which offers superior "
           "text shaping and better support for non-Latin text."));
 

+ 1 - 1
panda/src/text/fontPool.I

@@ -53,7 +53,7 @@ add_font(const std::string &filename, TextFont *font) {
 /**
  * Removes the indicated font from the pool, indicating it will never be
  * loaded again; the font may then be freed.  If this function is never
- * called, a reference count will be maintained on every font every loaded,
+ * called, a reference count will be maintained on every font ever loaded,
  * and fonts will never be freed.
  */
 INLINE void FontPool::

+ 41 - 0
panda/src/text/textAssembler.cxx

@@ -1411,6 +1411,10 @@ assemble_row(TextAssembler::TextRow &row,
   PN_stdfloat xpos = 0.0f;
   align = TextProperties::A_left;
 
+  TextProperties::Direction dir = TextProperties::D_ltr;
+  size_t rtl_begin = 0;
+  PN_stdfloat rtl_begin_xpos = 0.0f;
+
   // Remember previous character, for kerning.
   int prev_char = -1;
 
@@ -1472,6 +1476,8 @@ assemble_row(TextAssembler::TextRow &row,
         // Shape the buffer accumulated so far.
         shape_buffer(harfbuff, placed_glyphs, xpos, prev_cprops->_properties);
         hb_buffer_reset(harfbuff);
+        rtl_begin = placed_glyphs.size();
+        rtl_begin_xpos = xpos;
 
       } else if (harfbuff == nullptr && text_use_harfbuzz &&
                  font->is_of_type(DynamicTextFont::get_class_type())) {
@@ -1486,6 +1492,29 @@ assemble_row(TextAssembler::TextRow &row,
     }
 #endif
 
+    // When not using harfbuzz, check if the direction was set explicitly.
+    if (properties->has_direction() && properties->get_direction() != dir) {
+      if (dir == TextProperties::D_rtl) {
+        // Flip the preceding characters.
+        PN_stdfloat xpos2 = xpos;
+        size_t i = placed_glyphs.size();
+        while (i > rtl_begin) {
+          --i;
+          PN_stdfloat new_xpos = rtl_begin_xpos + (xpos - xpos2);
+          xpos2 = placed_glyphs[i]._xpos;
+          placed_glyphs[i]._xpos = new_xpos;
+        }
+        dir = TextProperties::D_ltr;
+      }
+      else if (dir == TextProperties::D_ltr) {
+        // When switching to right-to-left, keep track of the first glyph that
+        // was RTL so that we can later offset the X positions.
+        dir = TextProperties::D_rtl;
+        rtl_begin = placed_glyphs.size();
+        rtl_begin_xpos = xpos;
+      }
+    }
+
     if (character == ' ') {
       // A space is a special case.
       xpos += properties->get_glyph_scale() * properties->get_text_scale() * font->get_space_advance();
@@ -1651,6 +1680,18 @@ assemble_row(TextAssembler::TextRow &row,
     }
   }
 
+  if (dir == TextProperties::D_rtl) {
+    // Flip the preceding characters.
+    PN_stdfloat xpos2 = xpos;
+    size_t i = placed_glyphs.size();
+    while (i > rtl_begin) {
+      --i;
+      PN_stdfloat new_xpos = rtl_begin_xpos + (xpos - xpos2);
+      xpos2 = placed_glyphs[i]._xpos;
+      placed_glyphs[i]._xpos = new_xpos;
+    }
+  }
+
 #ifdef HAVE_HARFBUZZ
   if (harfbuff != nullptr && hb_buffer_get_length(harfbuff) > 0) {
     shape_buffer(harfbuff, placed_glyphs, xpos, prev_cprops->_properties);

+ 8 - 8
panda/src/windisplay/winGraphicsWindow.cxx

@@ -676,18 +676,18 @@ initialize_input_devices() {
   _input = device;
 
   // Get the number of devices.
-  if (GetRawInputDeviceList(nullptr, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0) {
+  if ((int)GetRawInputDeviceList(nullptr, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0) {
     return;
   }
 
   // Allocate the array to hold the DeviceList
   pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(sizeof(RAWINPUTDEVICELIST) * nInputDevices);
-  if (pRawInputDeviceList==0) {
+  if (pRawInputDeviceList == nullptr) {
     return;
   }
 
   // Fill the Array
-  if (GetRawInputDeviceList(pRawInputDeviceList, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) == -1) {
+  if ((int)GetRawInputDeviceList(pRawInputDeviceList, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) == -1) {
     return;
   }
 
@@ -696,13 +696,13 @@ initialize_input_devices() {
     if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE) {
       // Fetch information about specified mouse device.
       UINT nSize;
-      if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)0, &nSize) != 0) {
-        return;
+      if ((int)GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)nullptr, &nSize) != 0) {
+        continue;
       }
       char *psName = (char*)alloca(sizeof(TCHAR) * nSize);
-      if (psName == 0) return;
-      if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)psName, &nSize) < 0) {
-        return;
+      if (psName == nullptr) continue;
+      if ((int)GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)psName, &nSize) < 0) {
+        continue;
       }
 
       // If it's not an RDP mouse, add it to the list of raw mice.

+ 1 - 1
pandatool/src/egg-palettize/txaFileFilter.cxx

@@ -92,7 +92,7 @@ post_load(Texture *tex) {
   egg_tex->set_alpha_mode(tex_image.get_alpha_mode());
   egg_tex->set_format(props._format);
   egg_tex->set_minfilter(props._minfilter);
-  egg_tex->set_minfilter(props._magfilter);
+  egg_tex->set_magfilter(props._magfilter);
   egg_tex->set_anisotropic_degree(props._anisotropic_degree);
 
   tex->set_aux_data("egg", egg_tex);

+ 2 - 2
tests/display/test_cg_shader.py

@@ -18,14 +18,14 @@ def run_cg_compile_check(gsg, shader_path, expect_fail=False):
         assert shader is not None
 
 
[email protected](platform.machine().lower() == 'arm64', reason="Cg not supported on arm64")
[email protected](platform.machine().lower() in ('arm64', 'aarch64'), reason="Cg not supported on arm64")
 def test_cg_compile_error(gsg):
     """Test getting compile errors from bad Cg shaders"""
     shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha')
     run_cg_compile_check(gsg, shader_path, expect_fail=True)
 
 
[email protected](platform.machine().lower() == 'arm64', reason="Cg not supported on arm64")
[email protected](platform.machine().lower() in ('arm64', 'aarch64'), reason="Cg not supported on arm64")
 def test_cg_from_file(gsg):
     """Test compiling Cg shaders from files"""
     shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha')

+ 15 - 0
tests/gui/test_DirectButton.py

@@ -1,6 +1,21 @@
 from direct.gui.DirectButton import DirectButton
 
 
+def test_button_default_extraArgs():
+    btn = DirectButton()
+
+    assert btn.configure('extraArgs') == ('extraArgs', [], [])
+    assert btn._optionInfo['extraArgs'] == [[], [], None]
+
+    # Changing will not affect default value
+    btn['extraArgs'].append(1)
+    assert btn.configure('extraArgs') == ('extraArgs', [], [1])
+
+    # Changing this does
+    btn.configure('extraArgs')[1].append(2)
+    assert btn.configure('extraArgs') == ('extraArgs', [2], [1])
+
+
 def test_button_destroy():
     btn = DirectButton(text="Test")
     btn.destroy()