Просмотр исходного кода

Merge branch 'shaderpipeline' into vulkan

rdb 3 лет назад
Родитель
Сommit
0b2957925d
100 измененных файлов с 2229 добавлено и 912 удалено
  1. 10 10
      .github/workflows/ci.yml
  2. 5 29
      CMakeLists.txt
  3. 24 27
      README.md
  4. 12 77
      cmake/macros/BuildMetalib.cmake
  5. 2 15
      cmake/macros/PackageConfig.cmake
  6. 12 0
      cmake/modules/FindGTK3.cmake
  7. 53 33
      cmake/modules/FindLibSquish.cmake
  8. 0 88
      cmake/modules/FindOpenEXR.cmake
  9. 5 5
      contrib/src/rplight/shadowSource.h
  10. 110 67
      direct/src/dist/FreezeTool.py
  11. 5 0
      direct/src/dist/_android.py
  12. 71 51
      direct/src/dist/commands.py
  13. 2 1
      direct/src/distributed/ConnectionRepository.py
  14. 13 0
      direct/src/distributed/PyDatagramIterator.py
  15. 4 5
      direct/src/distributed/cConnectionRepository.cxx
  16. 15 5
      direct/src/filter/CommonFilters.py
  17. 3 3
      direct/src/filter/FilterManager.py
  18. 2 1
      direct/src/filter/filterBloomI.py
  19. 3 2
      direct/src/filter/filterBloomX.py
  20. 2 1
      direct/src/filter/filterBloomY.py
  21. 4 4
      direct/src/filter/filterBlurX.py
  22. 4 4
      direct/src/filter/filterBlurY.py
  23. 3 2
      direct/src/filter/filterCopy.py
  24. 3 2
      direct/src/filter/filterDown4.py
  25. 0 6
      direct/src/gui/DirectGuiBase.py
  26. 2 1
      direct/src/motiontrail/cMotionTrail.h
  27. 4 4
      direct/src/showbase/BufferViewer.py
  28. 2 2
      direct/src/showbase/DirectObject.py
  29. 1 1
      direct/src/showbase/JobManager.py
  30. 1 107
      direct/src/showbase/PythonUtil.py
  31. 8 2
      direct/src/showbase/ShowBase.py
  32. 7 7
      direct/src/stdpy/threading.py
  33. 26 15
      direct/src/task/Task.py
  34. 1 1
      doc/INSTALL
  35. 60 0
      doc/ReleaseNotes
  36. 12 32
      dtool/CompilerFlags.cmake
  37. 42 5
      dtool/Config.cmake
  38. 41 24
      dtool/Package.cmake
  39. 0 4
      dtool/PandaVersion.cmake
  40. 5 1
      dtool/dtool_config.h.in
  41. 211 93
      dtool/metalibs/dtoolconfig/pydtool.cxx
  42. 2 2
      dtool/src/cppparser/cppManifest.cxx
  43. 1 1
      dtool/src/cppparser/cppManifest.h
  44. 2 2
      dtool/src/cppparser/cppNamespace.cxx
  45. 2 2
      dtool/src/cppparser/cppStructType.cxx
  46. 8 1
      dtool/src/dtoolbase/CMakeLists.txt
  47. 1 1
      dtool/src/dtoolbase/deletedBufferChain.I
  48. 13 11
      dtool/src/dtoolbase/deletedBufferChain.cxx
  49. 3 2
      dtool/src/dtoolbase/deletedBufferChain.h
  50. 11 1
      dtool/src/dtoolbase/dtoolbase.h
  51. 0 5
      dtool/src/dtoolbase/dtoolbase_cc.h
  52. 12 0
      dtool/src/dtoolbase/memoryHook.cxx
  53. 0 2
      dtool/src/dtoolbase/mutexSpinlockImpl.h
  54. 1 1
      dtool/src/dtoolbase/mutexWin32Impl.cxx
  55. 1 0
      dtool/src/dtoolbase/p3dtoolbase_composite2.cxx
  56. 472 0
      dtool/src/dtoolbase/patomic.I
  57. 168 0
      dtool/src/dtoolbase/patomic.cxx
  58. 175 0
      dtool/src/dtoolbase/patomic.h
  59. 14 16
      dtool/src/dtoolbase/typeHandle.cxx
  60. 0 1
      dtool/src/dtoolbase/typeHandle_ext.cxx
  61. 1 2
      dtool/src/dtoolbase/typeRegistryNode.cxx
  62. 2 1
      dtool/src/dtoolbase/typeRegistryNode.h
  63. 39 6
      dtool/src/dtoolutil/executionEnvironment.cxx
  64. 15 1
      dtool/src/dtoolutil/filename.cxx
  65. 2 0
      dtool/src/dtoolutil/pandaSystem.cxx
  66. 10 2
      dtool/src/dtoolutil/panda_getopt_impl.cxx
  67. 3 4
      dtool/src/dtoolutil/pfstreamBuf.cxx
  68. 3 0
      dtool/src/interrogate/functionRemap.cxx
  69. 4 4
      dtool/src/interrogate/interfaceMaker.cxx
  70. 2 2
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  71. 2 2
      dtool/src/interrogate/interfaceMakerPythonObj.cxx
  72. 2 2
      dtool/src/interrogate/interfaceMakerPythonSimple.cxx
  73. 7 0
      dtool/src/interrogate/interrogate.cxx
  74. 22 3
      dtool/src/interrogate/interrogateBuilder.cxx
  75. 40 0
      dtool/src/interrogate/typeManager.cxx
  76. 1 0
      dtool/src/interrogate/typeManager.h
  77. 11 0
      dtool/src/interrogatedb/interrogateDatabase.cxx
  78. 16 0
      dtool/src/interrogatedb/interrogateFunction.I
  79. 6 1
      dtool/src/interrogatedb/interrogateFunction.h
  80. 11 0
      dtool/src/interrogatedb/interrogateFunctionWrapper.I
  81. 2 0
      dtool/src/interrogatedb/interrogateFunctionWrapper.h
  82. 67 1
      dtool/src/interrogatedb/interrogate_interface.cxx
  83. 13 1
      dtool/src/interrogatedb/interrogate_interface.h
  84. 2 2
      dtool/src/interrogatedb/py_panda.cxx
  85. 6 0
      dtool/src/prc/CMakeLists.txt
  86. 1 0
      dtool/src/prc/configFlags.h
  87. 2 1
      dtool/src/prc/configVariableBase.h
  88. 2 1
      dtool/src/prc/configVariableCore.h
  89. 2 5
      dtool/src/prc/notify.cxx
  90. 1 1
      dtool/src/prc/notifyCategory.I
  91. 7 2
      makepanda/installpanda.py
  92. 60 26
      makepanda/makepackage.py
  93. 117 44
      makepanda/makepanda.py
  94. 45 11
      makepanda/makepandacore.py
  95. 21 1
      makepanda/makewheel.py
  96. 6 1
      makepanda/test_wheel.py
  97. BIN
      models/plugin_images/auth_click.png
  98. BIN
      models/plugin_images/auth_ready.png
  99. BIN
      models/plugin_images/auth_rollover.png
  100. BIN
      models/plugin_images/download.png

+ 10 - 10
.github/workflows/ci.yml

@@ -333,7 +333,7 @@ jobs:
     if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
     strategy:
       matrix:
-        os: [ubuntu-18.04, windows-2016, macOS-11]
+        os: [ubuntu-18.04, windows-2019, macOS-11]
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v1
@@ -347,16 +347,16 @@ jobs:
       shell: powershell
       run: |
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.10/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.11/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz
-        tar -xf panda3d-1.10.10-tools-mac.tar.gz
-        mv panda3d-1.10.10/thirdparty thirdparty
-        rmdir panda3d-1.10.10
+        curl -O https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-mac.tar.gz
+        tar -xf panda3d-1.10.11-tools-mac.tar.gz
+        mv panda3d-1.10.11/thirdparty thirdparty
+        rmdir panda3d-1.10.11
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
     - name: Set up Python 3.9
       uses: actions/setup-python@v2
@@ -365,7 +365,7 @@ jobs:
     - name: Build Python 3.9
       shell: bash
       run: |
-        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
     - name: Test Python 3.9
       shell: bash
       run: |
@@ -378,7 +378,7 @@ jobs:
     - name: Build Python 3.8
       shell: bash
       run: |
-        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
     - name: Test Python 3.8
       shell: bash
       run: |
@@ -391,7 +391,7 @@ jobs:
     - name: Build Python 3.7
       shell: bash
       run: |
-        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
     - name: Test Python 3.7
       shell: bash
       run: |

+ 5 - 29
CMakeLists.txt

@@ -1,36 +1,14 @@
-cmake_minimum_required(VERSION 3.0.2)
+cmake_minimum_required(VERSION 3.13)
 set(CMAKE_DISABLE_SOURCE_CHANGES ON) # Must go before project() below
 set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) # Must go before project() below
 
-if(CMAKE_VERSION VERSION_GREATER "3.11" OR POLICY CMP0072)
-  # Prefer GLVND over libGL when available; this will be enabled by default
-  # once the minimum CMake version is at least 3.11.
-  cmake_policy(SET CMP0072 NEW)
-endif()
-
-if(CMAKE_VERSION VERSION_GREATER "3.12" OR POLICY CMP0074)
-  # Needed for THIRDPARTY_DIRECTORY support; this will be enabled by default
-  # once the minimum CMake version is at least 3.12.
-  cmake_policy(SET CMP0074 NEW)
-endif()
-
 if(POLICY CMP0091)
   # Needed for CMake to pass /MD flag properly with non-VC generators.
   cmake_policy(SET CMP0091 NEW)
 endif()
 
 # Determine whether we are using a multi-config generator.
-if(CMAKE_VERSION VERSION_GREATER "3.8")
-  get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
-else()
-  message(WARNING "Multi-configuration builds may not work properly when using
-a CMake < 3.9. Making a guess if this is a multi-config generator.")
-  if(DEFINED CMAKE_CONFIGURATION_TYPES)
-    set(IS_MULTICONFIG ON)
-  else()
-    set(IS_MULTICONFIG OFF)
-  endif()
-endif()
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
 
 # Set the default CMAKE_BUILD_TYPE before calling project().
 if(IS_MULTICONFIG)
@@ -89,11 +67,9 @@ string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INT
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/")
 
-if(CMAKE_VERSION VERSION_GREATER "3.8")
-  # When using the Xcode generator, don't append the platform name to the
-  # intermediate configuration directory.
-  set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME OFF)
-endif()
+# When using the Xcode generator, don't append the platform name to the
+# intermediate configuration directory.
+set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME OFF)
 
 # Include modules builtin to CMake
 include(GNUInstallDirs)     # Defines CMAKE_INSTALL_<dir> variables

+ 24 - 27
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-10/).
+[this page](https://www.panda3d.org/download/sdk-1-10-11/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -58,14 +58,14 @@ You will also need to install the [Windows SDK](https://developer.microsoft.com/
 and if you intend to target Windows Vista, you will also need the
 [Windows 8.1 SDK](https://go.microsoft.com/fwlink/p/?LinkId=323507).
 
-You will also need to have the third-party dependency libraries available for
+You will also need the thirdparty dependency libraries available for
 the build scripts to use.  These are available from one of these two URLs,
 depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 
-- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win32.zip
 
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
@@ -136,7 +136,7 @@ macOS
 -----
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-mac.tar.gz).
 
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
@@ -177,35 +177,32 @@ directory which you can install using `pkg install`.
 Android
 -------
 
-Note: building on Android is very experimental and not guaranteed to work.
+Although it's possible to build Panda3D on an Android device using the
+[termux](https://termux.com/) shell, the recommended route is to cross-compile
+.whl files using the SDK and NDK, which can then be used by the `build_apps`
+command to build a Python application into an .apk or .aab bundle.  You will
+need to get the latest thirdparty packages, which can be obtained from the
+artifacts page of the last successful run here:
 
-You can experimentally build the Android Python runner via the [termux](https://termux.com/)
-shell.  You will need to install [Termux](https://play.google.com/store/apps/details?id=com.termux)
-and [Termux API](https://play.google.com/store/apps/details?id=com.termux.api)
-from the Play Store.  Many of the dependencies can be installed by running the
-following command in the Termux shell:
+https://github.com/rdb/panda3d-thirdparty/actions?query=branch%3Amain+is%3Asuccess+event%3Apush
 
-```bash
-pkg install python ndk-sysroot clang bison freetype harfbuzz libpng eigen openal-soft opusfile libvorbis assimp libopus ecj dx patchelf aapt apksigner libcrypt openssl pkg-config
-```
+This does not include Python at the moment, which can be extracted from
+[this archive](https://rdb.name/thirdparty-android.tar.gz) instead.
 
-Then, you can build the .apk using this command:
+These commands show how to compile wheels for the supported Android ABIs:
 
 ```bash
-python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
+export ANDROID_SDK_ROOT=/home/rdb/local/android
+python3.8 makepanda/makepanda.py --everything --outputdir built-droid-arm64 --arch arm64 --target android-21 --threads 6 --wheel
+python3.8 makepanda/makepanda.py --everything --outputdir built-droid-armv7a --arch armv7a --target android-19 --threads 6 --wheel
+python3.8 makepanda/makepanda.py --everything --outputdir built-droid-x86_64 --arch x86_64 --target android-21 --threads 6 --wheel
+python3.8 makepanda/makepanda.py --everything --outputdir built-droid-x86 --arch x86 --target android-19 --threads 6 --wheel
 ```
 
-You can install the generated panda3d.apk by browsing to the panda3d folder
-using a file manager.  You may need to copy it to `/sdcard` to be able to
-access it from other apps.
-
-To launch a Python program from Termux, you can use the `run_python.sh` script
-inside the `panda/src/android` directory.  It will launch Python in a separate
-activity, load it with the Python script you passed as argument, and use a
-socket for returning the command-line output to the Termux shell.  Do note
-that this requires the Python application to reside on the SD card and that
-Termux needs to be set up with access to the SD card (using the
-`termux-setup-storage` command).
+It is now possible to use the generated wheels with `build_apps`, as explained
+on this page:
+
+https://discourse.panda3d.org/t/deployment-for-android/28226
 
 Running Tests
 =============

+ 12 - 77
cmake/macros/BuildMetalib.cmake

@@ -5,83 +5,6 @@
 #   instead just an agglomeration of the various component libraries that get
 #   linked into them. A library of libraries - a "metalibrary."
 
-#
-# Function: target_link_libraries(...)
-#
-# Overrides CMake's target_link_libraries() to support "linking" object
-# libraries. This is a partial reimplementation of CMake commit dc38970f83,
-# which is only available in CMake 3.12+
-#
-if(CMAKE_VERSION VERSION_LESS "3.12")
-  function(target_link_libraries target)
-    get_target_property(target_type "${target}" TYPE)
-    if(NOT target_type STREQUAL "OBJECT_LIBRARY")
-      _target_link_libraries("${target}" ${ARGN})
-      return()
-    endif()
-
-    foreach(library ${ARGN})
-      # This is a quick and dirty regex to tell targets apart from other stuff.
-      # It just checks if it's alphanumeric and starts with p3/panda.
-      if(library MATCHES "^(PKG::|p3|panda)[A-Za-z0-9]*$")
-        # We need to add "library"'s include directories to "target"
-        # (and transitively to INTERFACE_INCLUDE_DIRECTORIES so further
-        # dependencies will work)
-        set(include_directories "$<TARGET_PROPERTY:${library},INTERFACE_INCLUDE_DIRECTORIES>")
-        set_property(TARGET "${target}" APPEND PROPERTY INCLUDE_DIRECTORIES "${include_directories}")
-        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${include_directories}")
-
-        # SYSTEM include directories should still be reported as SYSTEM, so
-        # that warnings from those includes are suppressed
-        set(sys_include_directories
-          "$<TARGET_PROPERTY:${library},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>")
-        target_include_directories("${target}" SYSTEM PUBLIC "${sys_include_directories}")
-
-        # And for INTERFACE_COMPILE_DEFINITIONS as well
-        set(compile_definitions "$<TARGET_PROPERTY:${library},INTERFACE_COMPILE_DEFINITIONS>")
-        set_property(TARGET "${target}" APPEND PROPERTY COMPILE_DEFINITIONS "${compile_definitions}")
-        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "${compile_definitions}")
-
-        # Build up some generator expressions for determining whether `library`
-        # is a component library or not.
-        if(library MATCHES ".*::.*")
-          # "::" messes up CMake's genex parser; fortunately, a library whose
-          # name contains that is either an interface library or alias, and
-          # definitely not a component
-          set(is_component 0)
-          set(name_of_component "")
-          set(name_of_non_component "${library}")
-
-        else()
-          set(is_component "$<TARGET_PROPERTY:${library},IS_COMPONENT>")
-
-          # CMake complains if we lookup IS_COMPONENT on an INTERFACE library :(
-          set(is_object "$<STREQUAL:$<TARGET_PROPERTY:${library},TYPE>,OBJECT_LIBRARY>")
-          set(is_component "$<BOOL:$<${is_object}:${is_component}>>")
-
-          set(name_of_component "$<${is_component}:$<TARGET_NAME:${library}>>")
-          set(name_of_non_component "$<$<NOT:${is_component}>:$<TARGET_NAME:${library}>>")
-
-        endif()
-
-        # Libraries are only linked transitively if they aren't components.
-        set_property(TARGET "${target}" APPEND PROPERTY
-          INTERFACE_LINK_LIBRARIES "${name_of_non_component}")
-
-      else()
-        # This is a file path to an out-of-tree library - this needs to be
-        # recorded so that the metalib can link them. (They aren't needed at
-        # all for the object libraries themselves, so they don't have to work
-        # transitively.)
-        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${library}")
-
-      endif()
-
-    endforeach(library)
-
-  endfunction(target_link_libraries)
-endif()
-
 #
 # Function: add_component_library(target [SYMBOL building_symbol]
 #                                 [SOURCES] [[NOINIT]/[INIT func [header]]])
@@ -305,6 +228,7 @@ function(add_metalib target_name)
   set(interface_defines)
   set(includes)
   set(libs)
+  set(link_options)
   set(component_init_funcs "")
   foreach(component ${components})
     if(NOT TARGET "${component}")
@@ -392,6 +316,16 @@ function(add_metalib target_name)
         endif()
       endforeach(component_library)
 
+      # All the linker options applied to an individual component get applied
+      # when building the metalib (for things like --exclude-libs).
+      get_target_property(component_link_options "${component}" LINK_OPTIONS)
+      foreach(component_link_option ${component_link_options})
+        if(component_link_option)
+          list(APPEND link_options "${component_link_option}")
+
+        endif()
+      endforeach(component_link_option)
+
       # Consume this component's objects
       list(APPEND sources "$<TARGET_OBJECTS:${component}>")
 
@@ -419,6 +353,7 @@ function(add_metalib target_name)
     PRIVATE ${private_defines}
     INTERFACE ${interface_defines})
   target_link_libraries("${target_name}" ${libs})
+  target_link_options("${target_name}" PRIVATE ${link_options})
   target_include_directories("${target_name}"
     PUBLIC ${includes}
     INTERFACE "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/panda3d>")

+ 2 - 15
cmake/macros/PackageConfig.cmake

@@ -176,18 +176,6 @@ function(package_option name)
   # Create the INTERFACE library used to depend on this package.
   add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
 
-  # Explicitly record the package's include directories as system include
-  # directories.  CMake does do this automatically for INTERFACE libraries, but
-  # it does it by discovering all transitive links first, then reading
-  # INTERFACE_INCLUDE_DIRECTORIES for those which are INTERFACE libraries.  So,
-  # this would be broken for the metalib system (pre CMake 3.12) which doesn't
-  # "link" the object libraries.
-  if(CMAKE_VERSION VERSION_LESS "3.12")
-    set_target_properties(PKG::${name} PROPERTIES
-      INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
-      "$<TARGET_PROPERTY:PKG::${name},INTERFACE_INCLUDE_DIRECTORIES>")
-  endif()
-
   # If the option actually is enabled, populate the INTERFACE library created above
   if(HAVE_${name})
     set(use_variables ON)
@@ -412,9 +400,8 @@ function(export_packages filename)
             endforeach(config)
           endif()
 
-        elseif(CMAKE_VERSION VERSION_GREATER "3.8")
-          # This is an INTERFACE_LIBRARY, and CMake is new enough to support
-          # IMPORTED_IMPLIB
+        else()
+          # This is an INTERFACE_LIBRARY.
           get_target_property(imported_libname "${head}" IMPORTED_LIBNAME)
           if(imported_libname)
             list(APPEND libraries ${imported_libname})

+ 12 - 0
cmake/modules/FindGTK3.cmake

@@ -0,0 +1,12 @@
+find_package(PkgConfig QUIET)
+
+set(__gtk3_required_version "${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION}")
+if(__gtk3_required_version)
+    set(__gtk3_required_version " >= ${__gtk3_required_version}")
+endif()
+pkg_check_modules(GTK3 QUIET "gtk+-3.0${__gtk3_required_version}" IMPORTED_TARGET)
+
+if (NOT TARGET PkgConfig::GTK3)
+    set(GTK3_FOUND 0)
+endif()
+unset(__gtk3_required_version)

+ 53 - 33
cmake/modules/FindLibSquish.cmake

@@ -14,42 +14,62 @@
 #   LIBSQUISH_DEBUG_LIBRARY   - the filepath of the libsquish debug library
 #
 
-# Find the libsquish include files
-find_path(LIBSQUISH_INCLUDE_DIR
-  NAMES "squish.h"
-  PATHS "/usr/include"
-        "/usr/local/include"
-        "/sw/include"
-        "/opt/include"
-        "/opt/local/include"
-        "/opt/csw/include"
-  PATH_SUFFIXES "" "cppunit"
-)
+if(LibSquish_ROOT)
+  # Search exclusively under the root
+  find_path(LIBSQUISH_INCLUDE_DIR
+    NAMES "squish.h"
+    PATHS ${LibSquish_ROOT}
+    PATH_SUFFIXES "include"
+  )
 
-# Find the libsquish library built for release
-find_library(LIBSQUISH_RELEASE_LIBRARY
-  NAMES "squish" "libsquish"
-  PATHS "/usr"
-        "/usr/local"
-        "/usr/freeware"
-        "/sw"
-        "/opt"
-        "/opt/csw"
-  PATH_SUFFIXES "lib" "lib32" "lib64"
-)
+  find_library(LIBSQUISH_RELEASE_LIBRARY
+    NAMES "squish" "libsquish"
+    PATHS ${LibSquish_ROOT}
+    PATH_SUFFIXES "lib"
+  )
 
-# Find the libsquish library built for debug
-find_library(LIBSQUISH_DEBUG_LIBRARY
-  NAMES "squishd" "libsquishd"
-  PATHS "/usr"
-        "/usr/local"
-        "/usr/freeware"
-        "/sw"
-        "/opt"
-        "/opt/csw"
-  PATH_SUFFIXES "lib" "lib32" "lib64"
-)
+  find_library(LIBSQUISH_DEBUG_LIBRARY
+    NAMES "squishd" "libsquishd"
+    PATHS ${LibSquish_ROOT}
+    PATH_SUFFIXES "lib"
+  )
+else()
+  # Find the libsquish include files
+  find_path(LIBSQUISH_INCLUDE_DIR
+    NAMES "squish.h"
+    PATHS "/usr/include"
+          "/usr/local/include"
+          "/sw/include"
+          "/opt/include"
+          "/opt/local/include"
+          "/opt/csw/include"
+    PATH_SUFFIXES "" "cppunit"
+  )
 
+  # Find the libsquish library built for release
+  find_library(LIBSQUISH_RELEASE_LIBRARY
+    NAMES "squish" "libsquish"
+    PATHS "/usr"
+          "/usr/local"
+          "/usr/freeware"
+          "/sw"
+          "/opt"
+          "/opt/csw"
+    PATH_SUFFIXES "lib" "lib32" "lib64"
+  )
+
+  # Find the libsquish library built for debug
+  find_library(LIBSQUISH_DEBUG_LIBRARY
+    NAMES "squishd" "libsquishd"
+    PATHS "/usr"
+          "/usr/local"
+          "/usr/freeware"
+          "/sw"
+          "/opt"
+          "/opt/csw"
+    PATH_SUFFIXES "lib" "lib32" "lib64"
+  )
+endif()
 
 mark_as_advanced(LIBSQUISH_INCLUDE_DIR)
 mark_as_advanced(LIBSQUISH_RELEASE_LIBRARY)

+ 0 - 88
cmake/modules/FindOpenEXR.cmake

@@ -1,88 +0,0 @@
-# Filename: FindOpenEXR.cmake
-# Authors: CFSworks (5 Nov, 2018)
-#
-# Usage:
-#   find_package(OpenEXR [REQUIRED] [QUIET])
-#
-# Once done this will define:
-#   OPENEXR_FOUND       - system has OpenEXR
-#   OPENEXR_INCLUDE_DIR - the include directory containing OpenEXR header files
-#   OPENEXR_LIBRARIES   - the path to the OpenEXR libraries
-#
-
-find_path(OPENEXR_INCLUDE_DIR
-  "ImfVersion.h"
-  PATH_SUFFIXES "OpenEXR")
-
-mark_as_advanced(OPENEXR_INCLUDE_DIR)
-
-find_library(OPENEXR_imf_LIBRARY
-  NAMES "IlmImf")
-
-if(OPENEXR_imf_LIBRARY)
-  get_filename_component(_imf_dir "${OPENEXR_imf_LIBRARY}" DIRECTORY)
-  find_library(OPENEXR_imfutil_LIBRARY
-    NAMES "IlmImfUtil"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  find_library(OPENEXR_ilmthread_LIBRARY
-    NAMES "IlmThread"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  find_library(OPENEXR_iex_LIBRARY
-    NAMES "Iex"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  find_library(OPENEXR_iexmath_LIBRARY
-    NAMES "IexMath"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  find_library(OPENEXR_imath_LIBRARY
-    NAMES "Imath"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  find_library(OPENEXR_half_LIBRARY
-    NAMES "Half"
-    PATHS "${_imf_dir}"
-    NO_DEFAULT_PATH)
-
-  unset(_imf_dir)
-endif()
-
-mark_as_advanced(
-  OPENEXR_imf_LIBRARY
-  OPENEXR_imfutil_LIBRARY
-  OPENEXR_ilmthread_LIBRARY
-  OPENEXR_iex_LIBRARY
-  OPENEXR_iexmath_LIBRARY
-  OPENEXR_imath_LIBRARY
-  OPENEXR_half_LIBRARY
-)
-
-set(OPENEXR_LIBRARIES
-  ${OPENEXR_imf_LIBRARY}
-  ${OPENEXR_imfutil_LIBRARY}
-  ${OPENEXR_ilmthread_LIBRARY}
-  ${OPENEXR_iex_LIBRARY}
-  ${OPENEXR_iexmath_LIBRARY}
-  ${OPENEXR_imath_LIBRARY}
-  ${OPENEXR_half_LIBRARY}
-)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(OpenEXR DEFAULT_MSG
-  OPENEXR_INCLUDE_DIR OPENEXR_LIBRARIES
-
-  OPENEXR_imf_LIBRARY
-  OPENEXR_imfutil_LIBRARY
-  OPENEXR_ilmthread_LIBRARY
-  OPENEXR_iex_LIBRARY
-  OPENEXR_iexmath_LIBRARY
-  OPENEXR_imath_LIBRARY
-  OPENEXR_half_LIBRARY
-)

+ 5 - 5
contrib/src/rplight/shadowSource.h

@@ -30,6 +30,7 @@
 
 #include "pandabase.h"
 #include "luse.h"
+#include "memoryBase.h"
 #include "transformState.h"
 #include "look_at.h"
 #include "compose_matrix.h"
@@ -48,7 +49,7 @@
  *   and a view-projection matrix. The shadow manager regenerates the shadow maps
  *   using the data from the shadow sources.
  */
-class ShadowSource {
+class ShadowSource : public MemoryBase {
 public:
   ShadowSource();
 
@@ -78,14 +79,13 @@ public:
   inline const BoundingSphere& get_bounds() const;
 
 private:
-  int _slot;
-  bool _needs_update;
-  size_t _resolution;
   LMatrix4 _mvp;
   LVecBase4i _region;
   LVecBase4 _region_uv;
-
   BoundingSphere _bounds;
+  int _slot;
+  bool _needs_update;
+  size_t _resolution;
 };
 
 #include "shadowSource.I"

+ 110 - 67
direct/src/dist/FreezeTool.py

@@ -80,6 +80,7 @@ defaultHiddenImports = {
     ],
     'pandas.compat': ['lzma', 'cmath'],
     'pandas._libs.tslibs.conversion': ['pandas._libs.tslibs.base'],
+    'plyer': ['plyer.platforms'],
 }
 
 
@@ -91,6 +92,8 @@ ignoreImports = {
 
     'toml.encoder': ['numpy'],
     'py._builtin': ['__builtin__'],
+
+    'site': ['android_log'],
 }
 
 if sys.version_info >= (3, 8):
@@ -789,10 +792,6 @@ class Freezer:
         # default object will be created when it is needed.
         self.cenv = None
 
-        # This is the search path to use for Python modules.  Leave it
-        # to the default value of None to use sys.path.
-        self.path = path
-
         # The filename extension to append to the source file before
         # compiling.
         self.sourceExtension = '.c'
@@ -841,32 +840,44 @@ class Freezer:
         # builds.  It can be explicitly included if desired.
         self.modules['doctest'] = self.ModuleDef('doctest', exclude = True)
 
-        self.mf = None
-
         # Actually, make sure we know how to find all of the
         # already-imported modules.  (Some of them might do their own
         # special path mangling.)
         for moduleName, module in list(sys.modules.items()):
             if module and getattr(module, '__path__', None) is not None:
-                path = list(getattr(module, '__path__'))
-                if path:
-                    modulefinder.AddPackagePath(moduleName, path[0])
+                modPath = list(getattr(module, '__path__'))
+                if modPath:
+                    modulefinder.AddPackagePath(moduleName, modPath[0])
 
         # Module with non-obvious dependencies
         self.hiddenImports = defaultHiddenImports.copy()
         if hiddenImports is not None:
             self.hiddenImports.update(hiddenImports)
 
+        # Special hack for plyer, which has platform-specific hidden imports
+        plyer_platform = None
+        if self.platform.startswith('android'):
+            plyer_platform = 'android'
+        elif self.platform.startswith('linux'):
+            plyer_platform = 'linux'
+        elif self.platform.startswith('mac'):
+            plyer_platform = 'macosx'
+        elif self.platform.startswith('win'):
+            plyer_platform = 'win'
+
+        if plyer_platform:
+            self.hiddenImports['plyer'].append(f'plyer.platforms.{plyer_platform}.*')
+
         # Suffix/extension for Python C extension modules
         if self.platform == PandaSystem.getPlatform():
-            self.moduleSuffixes = imp.get_suffixes()
+            suffixes = imp.get_suffixes()
 
             # Set extension for Python files to binary mode
-            for i, suffix in enumerate(self.moduleSuffixes):
+            for i, suffix in enumerate(suffixes):
                 if suffix[2] == imp.PY_SOURCE:
-                    self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
+                    suffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
         else:
-            self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
+            suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
 
             abi_version = '{0}{1}'.format(*sys.version_info)
             abi_flags = ''
@@ -874,7 +885,7 @@ class Freezer:
                 abi_flags += 'm'
 
             if 'linux' in self.platform:
-                self.moduleSuffixes += [
+                suffixes += [
                     ('.cpython-{0}{1}-x86_64-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.cpython-{0}{1}-i686-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
@@ -882,24 +893,26 @@ class Freezer:
                 ]
             elif 'win' in self.platform:
                 # ABI flags are not appended on Windows.
-                self.moduleSuffixes += [
+                suffixes += [
                     ('.cp{0}-win_amd64.pyd'.format(abi_version), 'rb', 3),
                     ('.cp{0}-win32.pyd'.format(abi_version), 'rb', 3),
                     ('.pyd', 'rb', 3),
                 ]
             elif 'mac' in self.platform:
-                self.moduleSuffixes += [
+                suffixes += [
                     ('.cpython-{0}{1}-darwin.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
             else: # FreeBSD et al.
-                self.moduleSuffixes += [
+                suffixes += [
                     ('.cpython-{0}{1}.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
 
+        self.mf = PandaModuleFinder(excludes=['doctest'], suffixes=suffixes, path=path)
+
     def excludeFrom(self, freezer):
         """ Excludes all modules that have already been processed by
         the indicated FreezeTool.  This is equivalent to passing the
@@ -919,8 +932,6 @@ class Freezer:
         allowChildren is true, the children of the indicated module
         may still be included."""
 
-        assert self.mf is None
-
         self.modules[moduleName] = self.ModuleDef(
             moduleName, exclude = True,
             forbid = forbid, allowChildren = allowChildren,
@@ -945,24 +956,6 @@ class Freezer:
         files can be found.  If the module is a .py file and not a
         directory, returns None. """
 
-        # First, try to import the module directly.  That's the most
-        # reliable answer, if it works.
-        try:
-            module = __import__(moduleName)
-        except:
-            print("couldn't import %s" % (moduleName))
-            module = None
-
-        if module is not None:
-            for symbol in moduleName.split('.')[1:]:
-                module = getattr(module, symbol)
-            if hasattr(module, '__path__'):
-                return module.__path__
-
-        # If it didn't work--maybe the module is unimportable because
-        # it makes certain assumptions about the builtins, or
-        # whatever--then just look for file on disk.  That's usually
-        # good enough.
         path = None
         baseName = moduleName
         if '.' in baseName:
@@ -972,34 +965,20 @@ class Freezer:
                 return None
 
         try:
-            file, pathname, description = imp.find_module(baseName, path)
+            file, pathname, description = self.mf.find_module(baseName, path)
         except ImportError:
             return None
 
-        if not os.path.isdir(pathname):
+        if not self.mf._dir_exists(pathname):
             return None
+
         return [pathname]
 
     def getModuleStar(self, moduleName):
         """ Looks for the indicated directory module and returns the
         __all__ member: the list of symbols within the module. """
 
-        # First, try to import the module directly.  That's the most
-        # reliable answer, if it works.
-        try:
-            module = __import__(moduleName)
-        except:
-            print("couldn't import %s" % (moduleName))
-            module = None
-
-        if module is not None:
-            for symbol in moduleName.split('.')[1:]:
-                module = getattr(module, symbol)
-            if hasattr(module, '__all__'):
-                return module.__all__
-
-        # If it didn't work, just open the directory and scan for *.py
-        # files.
+        # Open the directory and scan for *.py files.
         path = None
         baseName = moduleName
         if '.' in baseName:
@@ -1009,16 +988,16 @@ class Freezer:
                 return None
 
         try:
-            file, pathname, description = imp.find_module(baseName, path)
+            file, pathname, description = self.mf.find_module(baseName, path)
         except ImportError:
             return None
 
-        if not os.path.isdir(pathname):
+        if not self.mf._dir_exists(pathname):
             return None
 
         # Scan the directory, looking for .py files.
         modules = []
-        for basename in sorted(os.listdir(pathname)):
+        for basename in sorted(self.mf._listdir(pathname)):
             if basename.endswith('.py') and basename != '__init__.py':
                 modules.append(basename[:-3])
 
@@ -1052,8 +1031,8 @@ class Freezer:
             modulePath = self.getModulePath(topName)
             if modulePath:
                 for dirname in modulePath:
-                    for basename in sorted(os.listdir(dirname)):
-                        if os.path.exists(os.path.join(dirname, basename, '__init__.py')):
+                    for basename in sorted(self.mf._listdir(dirname)):
+                        if self.mf._file_exists(os.path.join(dirname, basename, '__init__.py')):
                             parentName = '%s.%s' % (topName, basename)
                             newParentName = '%s.%s' % (newTopName, basename)
                             if self.getModulePath(parentName):
@@ -1098,8 +1077,6 @@ class Freezer:
         directories within a particular directory.
         """
 
-        assert self.mf is None
-
         if not newName:
             newName = moduleName
 
@@ -1120,8 +1097,6 @@ class Freezer:
         to done(), you may not add any more modules until you call
         reset(). """
 
-        assert self.mf is None
-
         # If we are building an exe, we also need to implicitly
         # bring in Python's startup modules.
         if addStartupModules:
@@ -1163,7 +1138,9 @@ class Freezer:
             else:
                 includes.append(mdef)
 
-        self.mf = PandaModuleFinder(excludes=list(excludeDict.keys()), suffixes=self.moduleSuffixes, path=self.path)
+        # Add the excludes to the ModuleFinder.
+        for exclude in excludeDict:
+            self.mf.excludes.append(exclude)
 
         # Attempt to import the explicit modules into the modulefinder.
 
@@ -1210,6 +1187,18 @@ class Freezer:
                 else:
                     self.__loadModule(self.ModuleDef(modname, implicit = True))
 
+        # Special case for sysconfig, which depends on a platform-specific
+        # sysconfigdata module on POSIX systems.
+        if 'sysconfig' in self.mf.modules:
+            if sys.version_info >= (3, 6):
+                if 'linux' in self.platform:
+                    arch = self.platform.split('_', 1)[1]
+                    self.__loadModule(self.ModuleDef('_sysconfigdata__linux_' + arch + '-linux-gnu', implicit=True))
+                elif 'mac' in self.platform:
+                    self.__loadModule(self.ModuleDef('_sysconfigdata__darwin_darwin', implicit=True))
+            elif 'linux' in self.platform or 'mac' in self.platform:
+                self.__loadModule(self.ModuleDef('_sysconfigdata', implicit=True))
+
         # Now, any new modules we found get added to the export list.
         for origName in list(self.mf.modules.keys()):
             if origName not in origToNewName:
@@ -2406,7 +2395,13 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                     return None
 
                 try:
-                    fp = zip.open(fn.replace(os.path.sep, '/'), 'r')
+                    zip_fn = fn.replace(os.path.sep, '/')
+                    if zip_fn.startswith('deploy_libs/_tkinter.'):
+                        # If we have a tkinter wheel on the path, ignore the
+                        # _tkinter extension in deploy-libs.
+                        if any(entry.endswith(".whl") and os.path.basename(entry).startswith("tkinter-") for entry in self.path):
+                            return None
+                    fp = zip.open(zip_fn, 'r')
                 except KeyError:
                     return None
 
@@ -2420,6 +2415,17 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
         return None
 
+    def _file_exists(self, path):
+        if os.path.exists(path):
+            return os.path.isfile(path)
+
+        fh = self._open_file(path, 'rb')
+        if fh:
+            fh.close()
+            return True
+
+        return False
+
     def _dir_exists(self, path):
         """Returns True if the given directory exists, either on disk or inside
         a wheel."""
@@ -2458,6 +2464,43 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
         return False
 
+    def _listdir(self, path):
+        """Lists files in the given directory if it exists."""
+
+        if os.path.isdir(path):
+            return os.listdir(path)
+
+        # Is there a zip file along the path?
+        dir, dirname = os.path.split(path.rstrip(os.path.sep + '/'))
+        fn = dirname
+        while dirname:
+            if os.path.isfile(dir):
+                # Okay, this is actually a file.  Is it a zip file?
+                if dir in self._zip_files:
+                    # Yes, and we've previously opened this.
+                    zip = self._zip_files[dir]
+                elif zipfile.is_zipfile(dir):
+                    zip = zipfile.ZipFile(dir)
+                    self._zip_files[dir] = zip
+                else:
+                    # It's not a directory or zip file.
+                    return []
+
+                # List files whose path start with our directory name.
+                prefix = fn.replace(os.path.sep, '/') + '/'
+                result = []
+                for name in zip.namelist():
+                    if name.startswith(prefix) and '/' not in name[len(prefix):]:
+                        result.append(name[len(prefix):])
+
+                return result
+
+            # Look at the parent directory.
+            dir, dirname = os.path.split(dir)
+            fn = os.path.join(dirname, fn)
+
+        return []
+
     def load_module(self, fqname, fp, pathname, file_info):
         """Copied from ModuleFinder.load_module with fixes to handle sending bytes
         to compile() for PY_SOURCE types. Sending bytes to compile allows it to
@@ -2704,7 +2747,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         modules = {}
         for dir in m.__path__:
             try:
-                names = os.listdir(dir)
+                names = self._listdir(dir)
             except OSError:
                 self.msg(2, "can't list directory", dir)
                 continue

+ 5 - 0
direct/src/dist/_android.py

@@ -169,9 +169,11 @@ ANDROID_ATTRIBUTES = {
     'alwaysRetainTaskState': bool_resource(0x1010203),
     'clearTaskOnLaunch': bool_resource(0x1010015),
     'debuggable': bool_resource(0x0101000f),
+    'documentLaunchMode': enum_resource(0x1010445, "none", "intoExisting", "always", "never"),
     'configChanges': flag_resource(0x0101001f, mcc=0x0001, mnc=0x0002, locale=0x0004, touchscreen=0x0008, keyboard=0x0010, keyboardHidden=0x0020, navigation=0x0040, orientation=0x0080, screenLayout=0x0100, uiMode=0x0200, screenSize=0x0400, smallestScreenSize=0x0800, layoutDirection=0x2000, fontScale=0x40000000),
     'enabled': bool_resource(0x101000e),
     'excludeFromRecents': bool_resource(0x1010017),
+    'exported': bool_resource(0x1010010),
     'extractNativeLibs': bool_resource(0x10104ea),
     'finishOnTaskLaunch': bool_resource(0x1010014),
     'fullBackupContent': bool_resource(0x10104eb),
@@ -189,9 +191,12 @@ ANDROID_ATTRIBUTES = {
     'minSdkVersion': int_resource(0x101020c),
     'multiprocess': bool_resource(0x1010013),
     'name': str_resource(0x1010003),
+    'noHistory': bool_resource(0x101022d),
     'pathPattern': str_resource(0x101002c),
+    'resizeableActivity': bool_resource(0x10104f6),
     'required': bool_resource(0x101028e),
     'scheme': str_resource(0x1010027),
+    'screenOrientation': enum_resource(0x101001e, 'landscape', 'portrait', 'user', 'behind', 'sensor', 'nosensor', 'sensorLandscape', 'sensorPortrait', 'reverseLandscape', 'reversePortrait', 'fullSensor', 'userLandscape', 'userPortrait', 'fullUser', 'locked'),
     'stateNotNeeded': bool_resource(0x1010016),
     'supportsRtl': bool_resource(0x010103af),
     'supportsUploading': bool_resource(0x101029b),

+ 71 - 51
direct/src/dist/commands.py

@@ -96,6 +96,7 @@ PACKAGE_DATA_DIRS = {
     ],
     'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
     'certifi': [('certifi/cacert.pem', '', {})],
+    '_tkinter_ext': [('_tkinter_ext/tcl/**', 'tcl', {})],
 }
 
 # Some dependencies have extra directories that need to be scanned for DLLs.
@@ -115,7 +116,7 @@ from _frozen_importlib import _imp, FrozenImporter
 
 sys.frozen = True
 
-if sys.platform == 'win32':
+if sys.platform == 'win32' and sys.version_info < (3, 10):
     # Make sure the preferred encoding is something we actually support.
     import _bootlocale
     enc = _bootlocale.getpreferredencoding().lower()
@@ -140,21 +141,6 @@ def get_data(path):
 
 FrozenImporter.find_spec = find_spec
 FrozenImporter.get_data = get_data
-
-# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
-import os
-tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
-if os.path.isdir(tcl_dir):
-    for dir in os.listdir(tcl_dir):
-        sub_dir = os.path.join(tcl_dir, dir)
-        if os.path.isdir(sub_dir):
-            if dir.startswith('tcl'):
-                os.environ['TCL_LIBRARY'] = sub_dir
-            if dir.startswith('tk'):
-                os.environ['TK_LIBRARY'] = sub_dir
-            if dir.startswith('tix'):
-                os.environ['TIX_LIBRARY'] = sub_dir
-del os
 """
 
 SITE_PY_ANDROID = """
@@ -169,7 +155,9 @@ from android_log import write as android_log_write
 
 
 sys.frozen = True
-sys.platform = "android"
+
+# Temporary hack for plyer to detect Android, see kivy/plyer#670
+os.environ['ANDROID_ARGUMENT'] = ''
 
 
 # Replace stdout/stderr with something that writes to the Android log.
@@ -277,7 +265,7 @@ class build_apps(setuptools.Command):
         self.exclude_modules = {}
         self.icons = {}
         self.platforms = [
-            'manylinux2010_x86_64',
+            'manylinux2014_x86_64',
             'macosx_10_9_x86_64',
             'win_amd64',
         ]
@@ -479,6 +467,12 @@ class build_apps(setuptools.Command):
         elif not self.android_abis:
             self.android_abis = ['arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86']
 
+        supported_abis = 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
+        unsupported_abis = set(self.android_abis) - set(supported_abis)
+        if unsupported_abis:
+            raise ValueError(f'Unrecognized value(s) for android_abis: {", ".join(unsupported_abis)}\n'
+                             f'Valid ABIs are: {", ".join(supported_abis)}')
+
         self.icon_objects = {}
         for app, iconpaths in self.icons.items():
             if not isinstance(iconpaths, list) and not isinstance(iconpaths, tuple):
@@ -527,7 +521,9 @@ class build_apps(setuptools.Command):
                     else: # e.g. x86, x86_64, mips, mips64
                         suffix = '_' + abi.replace('-', '_')
 
-                    self.build_binaries(lib_dir, platform + suffix)
+                    # We end up copying the data multiple times to the same
+                    # directory, but that's probably fine for now.
+                    self.build_binaries(platform + suffix, lib_dir, data_dir)
 
                 # Write out the icons to the res directory.
                 for appname, icon in self.icon_objects.items():
@@ -546,13 +542,13 @@ class build_apps(setuptools.Command):
                     if icon.getLargestSize() >= 192:
                         icon.writeSize(192, os.path.join(res_dir, 'mipmap-xxxhdpi-v4', basename))
 
-                self.build_data(data_dir, platform)
+                self.build_assets(platform, data_dir)
 
                 # Generate an AndroidManifest.xml
                 self.generate_android_manifest(os.path.join(build_dir, 'AndroidManifest.xml'))
             else:
-                self.build_binaries(build_dir, platform)
-                self.build_data(build_dir, platform)
+                self.build_binaries(platform, build_dir, build_dir)
+                self.build_assets(platform, build_dir)
 
             # Bundle into an .app on macOS
             if self.macos_main_app and 'macosx' in platform:
@@ -595,10 +591,15 @@ class build_apps(setuptools.Command):
             '-d', whldir,
             '-r', self.requirements_path,
             '--only-binary', ':all:',
-            '--platform', platform,
             '--abi', abi_tag,
+            '--platform', platform,
         ]
 
+        if platform.startswith('linux_'):
+            # Also accept manylinux.
+            arch = platform[6:]
+            pip_args += ['--platform', 'manylinux2014_' + arch]
+
         if self.use_optimized_wheels:
             pip_args += [
                 '--extra-index-url', self.optimized_wheel_index
@@ -736,7 +737,7 @@ class build_apps(setuptools.Command):
 
         for appname in self.gui_apps:
             activity = ET.SubElement(application, 'activity')
-            activity.set('android:name', 'org.panda3d.android.PandaActivity')
+            activity.set('android:name', 'org.panda3d.android.PythonActivity')
             activity.set('android:label', appname)
             activity.set('android:theme', '@android:style/Theme.NoTitleBar')
             activity.set('android:configChanges', 'orientation|keyboardHidden')
@@ -759,13 +760,14 @@ class build_apps(setuptools.Command):
         with open(path, 'wb') as fh:
             tree.write(fh, encoding='utf-8', xml_declaration=True)
 
-    def build_binaries(self, binary_dir, platform):
+    def build_binaries(self, platform, binary_dir, data_dir=None):
         """ Builds the binary data for the given platform. """
 
         use_wheels = True
         path = sys.path[:]
         p3dwhl = None
         wheelpaths = []
+        has_tkinter_wheel = False
 
         if use_wheels:
             wheelpaths = self.download_wheels(platform)
@@ -775,6 +777,8 @@ class build_apps(setuptools.Command):
                     p3dwhlfn = whl
                     p3dwhl = self._get_zip_file(p3dwhlfn)
                     break
+                elif os.path.basename(whl).startswith('tkinter-'):
+                    has_tkinter_wheel = True
             else:
                 raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform))
 
@@ -787,6 +791,11 @@ class build_apps(setuptools.Command):
                         distutils.log.WARN
                     )
 
+            for whl in wheelpaths:
+                if os.path.basename(whl).startswith('tkinter-'):
+                    has_tkinter_wheel = True
+                    break
+
             #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths}
 
             # Add whl files to the path so they are picked up by modulefinder
@@ -1016,7 +1025,7 @@ class build_apps(setuptools.Command):
 
             freezer_extras.update(freezer.extras)
             freezer_modules.update(freezer.getAllModuleNames())
-            for suffix in freezer.moduleSuffixes:
+            for suffix in freezer.mf.suffixes:
                 if suffix[2] == imp.C_EXTENSION:
                     ext_suffixes.add(suffix[0])
 
@@ -1026,13 +1035,14 @@ class build_apps(setuptools.Command):
         for appname, scriptname in self.console_apps.items():
             create_runtime(platform, appname, scriptname, True)
 
+        # Warn if tkinter is used but hasn't been added to requirements.txt
+        if not has_tkinter_wheel and '_tkinter' in freezer_modules:
+            self.warn("Detected use of tkinter, but tkinter is not specified in requirements.txt!")
+
         # Copy extension modules
-        whl_modules = []
-        whl_modules_ext = ''
+        whl_modules = {}
         if use_wheels:
             # Get the module libs
-            whl_modules = []
-
             for i in p3dwhl.namelist():
                 if not i.startswith('deploy_libs/'):
                     continue
@@ -1040,10 +1050,14 @@ class build_apps(setuptools.Command):
                 if not any(i.endswith(suffix) for suffix in ext_suffixes):
                     continue
 
+                if has_tkinter_wheel and i.startswith('deploy_libs/_tkinter.'):
+                    # Ignore this one, we have a separate tkinter package
+                    # nowadays that contains all the dependencies.
+                    continue
+
                 base = os.path.basename(i)
                 module, _, ext = base.partition('.')
-                whl_modules.append(module)
-                whl_modules_ext = ext
+                whl_modules[module] = i
 
         # Make sure to copy any builtins that have shared objects in the
         # deploy libs, assuming they are not already in freezer_extras.
@@ -1095,7 +1109,7 @@ class build_apps(setuptools.Command):
             else:
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 if module in whl_modules:
-                    source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext))#'{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext)
+                    source_path = os.path.join(p3dwhlfn, whl_modules[module])
                     basename = os.path.basename(source_path)
                     #XXX should we remove python version string here too?
                 else:
@@ -1111,28 +1125,15 @@ class build_apps(setuptools.Command):
             search_path = get_search_path_for(source_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
 
-        # Copy over the tcl directory.
-        #TODO: get this to work on non-Windows platforms.
-        if sys.platform == "win32" and platform.startswith('win'):
-            tcl_dir = os.path.join(sys.prefix, 'tcl')
-
-            if os.path.isdir(tcl_dir) and 'tkinter' in freezer_modules:
-                self.announce('Copying Tcl files', distutils.log.INFO)
-                os.makedirs(os.path.join(binary_dir, 'tcl'))
-
-                for dir in os.listdir(tcl_dir):
-                    sub_dir = os.path.join(tcl_dir, dir)
-                    if os.path.isdir(sub_dir):
-                        target_dir = os.path.join(binary_dir, 'tcl', dir)
-                        self.announce('copying {0} -> {1}'.format(sub_dir, target_dir))
-                        shutil.copytree(sub_dir, target_dir)
-
         # Copy classes.dex on Android
         if use_wheels and platform.startswith('android'):
             self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'),
                       os.path.join(binary_dir, '..', '..', 'classes.dex'))
 
         # Extract any other data files from dependency packages.
+        if data_dir is None:
+            return
+
         for module, datadesc in self.package_data_dirs.items():
             if module not in freezer_modules:
                 continue
@@ -1151,9 +1152,14 @@ class build_apps(setuptools.Command):
                     target_dir = os.path.join(data_dir, target_dir)
 
                     for wf in filenames:
+                        if wf.endswith('/'):
+                            # Skip directories.
+                            continue
+
                         if wf.lower().startswith(source_dir.lower() + '/'):
                             if not srcglob.matches(wf.lower()):
                                 continue
+
                             wf = wf.replace('/', os.sep)
                             relpath = wf[len(source_dir) + 1:]
                             source_path = os.path.join(whl, wf)
@@ -1168,11 +1174,11 @@ class build_apps(setuptools.Command):
                             else:
                                 self.copy(source_path, target_path)
 
-    def build_data(self, data_dir, platform):
+    def build_assets(self, platform, data_dir):
         """ Builds the data files for the given platform. """
 
         # Copy Game Files
-        self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
+        self.announce('Copying assets for platform: {}'.format(platform), distutils.log.INFO)
         ignore_copy_list = [
             '**/__pycache__/**',
             '**/*.pyc',
@@ -1597,6 +1603,20 @@ class bdist_apps(setuptools.Command):
         'manylinux1_i686': ['gztar'],
         'manylinux2010_x86_64': ['gztar'],
         'manylinux2010_i686': ['gztar'],
+        'manylinux2014_x86_64': ['gztar'],
+        'manylinux2014_i686': ['gztar'],
+        'manylinux2014_aarch64': ['gztar'],
+        'manylinux2014_armv7l': ['gztar'],
+        'manylinux2014_ppc64': ['gztar'],
+        'manylinux2014_ppc64le': ['gztar'],
+        'manylinux2014_s390x': ['gztar'],
+        'manylinux_2_24_x86_64': ['gztar'],
+        'manylinux_2_24_i686': ['gztar'],
+        'manylinux_2_24_aarch64': ['gztar'],
+        'manylinux_2_24_armv7l': ['gztar'],
+        'manylinux_2_24_ppc64': ['gztar'],
+        'manylinux_2_24_ppc64le': ['gztar'],
+        'manylinux_2_24_s390x': ['gztar'],
         'android': ['aab'],
         # Everything else defaults to ['zip']
     }

+ 2 - 1
direct/src/distributed/ConnectionRepository.py

@@ -9,7 +9,6 @@ from direct.showbase import GarbageReport
 from direct.showbase.MessengerGlobal import messenger
 from .PyDatagramIterator import PyDatagramIterator
 
-import inspect
 import gc
 
 __all__ = ["ConnectionRepository", "GCTrigger"]
@@ -311,6 +310,8 @@ class ConnectionRepository(
 
         # Now get the class definition for the classes named in the DC
         # file.
+        import inspect
+
         for i in range(dcFile.getNumClasses()):
             dclass = dcFile.getClass(i)
             number = dclass.getNumber()

+ 13 - 0
direct/src/distributed/PyDatagramIterator.py

@@ -30,6 +30,19 @@ class PyDatagramIterator(DatagramIterator):
 
     getChannel = DatagramIterator.getUint64
 
+    def __init__(self, datagram=None, offset=0):
+        if datagram is not None:
+            super().__init__(datagram, offset)
+
+            # Retain a reference to it so that it doesn't get deleted.
+            self.__initialDatagram = datagram
+        else:
+            super().__init__()
+
+    def assign(self, datagram, offset = 0):
+        super().assign(datagram, offset)
+        self.__initialDatagram = datagram
+
     def getArg(self, subatomicType, divisor=1):
         # Import the type numbers
         if divisor == 1:

+ 4 - 5
direct/src/distributed/cConnectionRepository.cxx

@@ -58,19 +58,18 @@ CConnectionRepository(bool has_owner_view, bool threaded_net) :
   _bdc(4096000,4096000,1400),
   _native(false),
 #endif
+  _has_owner_view(has_owner_view),
+  _handle_c_updates(true),
   _client_datagram(true),
   _handle_datagrams_internally(handle_datagrams_internally),
   _simulated_disconnect(false),
   _verbose(distributed_cat.is_spam()),
+  _in_quiet_zone(0),
   _time_warning(0.0),
-// _msg_channels(),
   _msg_sender(0),
   _msg_type(0),
-  _has_owner_view(has_owner_view),
-  _handle_c_updates(true),
   _want_message_bundling(true),
-  _bundling_msgs(0),
-  _in_quiet_zone(0)
+  _bundling_msgs(0)
 {
 #if defined(HAVE_NET) && defined(SIMULATE_NETWORK_DELAY)
   if (min_lag != 0.0 || max_lag != 0.0) {

+ 15 - 5
direct/src/filter/CommonFilters.py

@@ -20,6 +20,7 @@ from panda3d.core import LVecBase4, LPoint2
 from panda3d.core import AuxBitplaneAttrib
 from panda3d.core import Texture, Shader, ATSNone
 from panda3d.core import FrameBufferProperties
+from panda3d.core import getDefaultCoordinateSystem, CS_zup_right, CS_zup_left
 
 from direct.task.TaskManagerGlobal import taskMgr
 
@@ -52,6 +53,7 @@ o_color = lerp(o_color, k_cartooncolor, cartoon_thresh);
 SSAO_BODY = """//Cg
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float2 l_texcoord : TEXCOORD0,
              out float2 l_texcoordD : TEXCOORD1,
@@ -61,9 +63,9 @@ void vshader(float4 vtx_position : POSITION,
              uniform float4x4 mat_modelproj)
 {
   l_position = mul(mat_modelproj, vtx_position);
-  l_texcoord = vtx_position.xz;
-  l_texcoordD = (vtx_position.xz * texpad_depth.xy) + texpad_depth.xy;
-  l_texcoordN = (vtx_position.xz * texpad_normal.xy) + texpad_normal.xy;
+  l_texcoord = vtx_texcoord;
+  l_texcoordD = vtx_texcoord * texpad_depth.xy * 2;
+  l_texcoordN = vtx_texcoord * texpad_normal.xy * 2;
 }
 
 float3 sphere[16] = float3[](float3(0.53812504, 0.18565957, -0.43192),float3(0.13790712, 0.24864247, 0.44301823),float3(0.33715037, 0.56794053, -0.005789503),float3(-0.6999805, -0.04511441, -0.0019965635),float3(0.06896307, -0.15983082, -0.85477847),float3(0.056099437, 0.006954967, -0.1843352),float3(-0.014653638, 0.14027752, 0.0762037),float3(0.010019933, -0.1924225, -0.034443386),float3(-0.35775623, -0.5301969, -0.43581226),float3(-0.3169221, 0.106360726, 0.015860917),float3(0.010350345, -0.58698344, 0.0046293875),float3(-0.08972908, -0.49408212, 0.3287904),float3(0.7119986, -0.0154690035, -0.09183723),float3(-0.053382345, 0.059675813, -0.5411899),float3(0.035267662, -0.063188605, 0.54602677),float3(-0.47761092, 0.2847911, -0.0271716));
@@ -292,11 +294,19 @@ class CommonFilters:
             text += "{\n"
             text += "  l_position = mul(mat_modelproj, vtx_position);\n"
 
+            # The card is oriented differently depending on our chosen
+            # coordinate system.  We could just use vtx_texcoord, but this
+            # saves on an additional variable.
+            if getDefaultCoordinateSystem() in (CS_zup_right, CS_zup_left):
+                pos = "vtx_position.xz"
+            else:
+                pos = "vtx_position.xy"
+
             for texcoord, padTex in texcoordPadding.items():
                 if padTex is None:
-                    text += "  %s = vtx_position.xz * float2(0.5, 0.5) + float2(0.5, 0.5);\n" % (texcoord)
+                    text += "  %s = %s * float2(0.5, 0.5) + float2(0.5, 0.5);\n" % (texcoord, pos)
                 else:
-                    text += "  %s = (vtx_position.xz * texpad_tx%s.xy) + texpad_tx%s.xy;\n" % (texcoord, padTex, padTex)
+                    text += "  %s = (%s * texpad_tx%s.xy) + texpad_tx%s.xy;\n" % (texcoord, pos, padTex, padTex)
 
                     if "HalfPixelShift" in configuration:
                         text += "  %s += texpix_tx%s.xy * 0.5;\n" % (texcoord, padTex)

+ 3 - 3
direct/src/filter/FilterManager.py

@@ -123,8 +123,8 @@ class FilterManager(DirectObject):
             winy = winy // div
 
         if mul != 1:
-            winx = winx * mul
-            winy = winy * mul
+            winx = int(round(winx * mul))
+            winy = int(round(winy * mul))
 
         return winx,winy
 
@@ -323,7 +323,7 @@ class FilterManager(DirectObject):
             props.setAuxRgba(1)
         if auxtex1 is not None:
             props.setAuxRgba(2)
-        buffer=base.graphicsEngine.makeOutput(
+        buffer=self.engine.makeOutput(
             self.win.getPipe(), name, -1,
             props, winprops, GraphicsPipe.BFRefuseWindow | GraphicsPipe.BFResizeable,
             self.win.getGsg(), self.win)

+ 2 - 1
direct/src/filter/filterBloomI.py

@@ -32,6 +32,7 @@ BLOOM_I = """
 
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float2 l_texcoordNW : TEXCOORD0,
              out float2 l_texcoordNE : TEXCOORD1,
@@ -42,7 +43,7 @@ void vshader(float4 vtx_position : POSITION,
              uniform float4x4 mat_modelproj)
 {
   l_position=mul(mat_modelproj, vtx_position);
-  float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  float2 c = vtx_texcoord * texpad_src.xy * 2;
   float4 offs = texpix_src * 0.5;
   l_texcoordNW = c + float2( offs.x, -offs.y);
   l_texcoordNE = c + float2( offs.x,  offs.y);

+ 3 - 2
direct/src/filter/filterBloomX.py

@@ -2,6 +2,7 @@ BLOOM_X = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float4 l_texcoord0 : TEXCOORD0,
              out float4 l_texcoord1 : TEXCOORD1,
@@ -10,8 +11,8 @@ void vshader(float4 vtx_position : POSITION,
              uniform float4 texpix_src,
              uniform float4x4 mat_modelproj)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  l_position = mul(mat_modelproj, vtx_position);
+  float2 c = vtx_texcoord * texpad_src.xy * 2;
   float offset = texpix_src.x;
   float pad = texpad_src.x * 2;
   l_texcoord0 = float4(min(c.x-offset* -4, pad), min(c.x-offset* -3, pad), min(c.x-offset* -2, pad), c.y);

+ 2 - 1
direct/src/filter/filterBloomY.py

@@ -2,6 +2,7 @@ BLOOM_Y = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float4 l_texcoord0 : TEXCOORD0,
              out float4 l_texcoord1 : TEXCOORD1,
@@ -11,7 +12,7 @@ void vshader(float4 vtx_position : POSITION,
              uniform float4x4 mat_modelproj)
 {
   l_position=mul(mat_modelproj, vtx_position);
-  float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  float2 c = vtx_texcoord * texpad_src.xy * 2;
   float offset = texpix_src.y;
   float pad = texpad_src.y * 2;
   l_texcoord0 = float4(min(c.y-offset* -4, pad), min(c.y-offset* -3, pad), min(c.y-offset* -2, pad), c.x);

+ 4 - 4
direct/src/filter/filterBlurX.py

@@ -4,14 +4,14 @@ BLUR_X = """
 //Cg profile arbvp1 arbfp1
 
 void vshader(float4 vtx_position : POSITION,
-             float2 vtx_texcoord0 : TEXCOORD0,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
-      	     out float2 l_texcoord0 : TEXCOORD0,
+             out float2 l_texcoord0 : TEXCOORD0,
              uniform float4 texpad_src,
              uniform float4x4 mat_modelproj)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  l_texcoord0 = (vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  l_position = mul(mat_modelproj, vtx_position);
+  l_texcoord0 = vtx_texcoord * texpad_src.xy * 2;
 }
 
 

+ 4 - 4
direct/src/filter/filterBlurY.py

@@ -4,14 +4,14 @@ BLUR_Y = """
 //Cg profile arbvp1 arbfp1
 
 void vshader(float4 vtx_position : POSITION,
-             float2 vtx_texcoord0 : TEXCOORD0,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
-      	     out float2 l_texcoord0 : TEXCOORD0,
+             out float2 l_texcoord0 : TEXCOORD0,
              uniform float4 texpad_src,
              uniform float4x4 mat_modelproj)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  l_texcoord0 = (vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  l_position = mul(mat_modelproj, vtx_position);
+  l_texcoord0 = vtx_texcoord * texpad_src.xy * 2;
 }
 
 

+ 3 - 2
direct/src/filter/filterCopy.py

@@ -3,13 +3,14 @@ COPY = """
 
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float2 l_texcoord : TEXCOORD0,
              uniform float4 texpad_src,
              uniform float4x4 mat_modelproj)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  l_texcoord = (vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  l_position = mul(mat_modelproj, vtx_position);
+  l_texcoord = vtx_texcoord * texpad_src.xy * 2;
 }
 
 void fshader(float2 l_texcoord : TEXCOORD0,

+ 3 - 2
direct/src/filter/filterDown4.py

@@ -2,6 +2,7 @@ DOWN_4 = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord : TEXCOORD0,
              out float4 l_position : POSITION,
              out float2 l_texcoordNW : TEXCOORD0,
              out float2 l_texcoordNE : TEXCOORD1,
@@ -11,8 +12,8 @@ void vshader(float4 vtx_position : POSITION,
              uniform float4 texpix_src,
              uniform float4x4 mat_modelproj)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
+  l_position = mul(mat_modelproj, vtx_position);
+  float2 c = vtx_texcoord * texpad_src.xy * 2;
   l_texcoordNW = c + float2( texpix_src.x, -texpix_src.y);
   l_texcoordNE = c + float2( texpix_src.x,  texpix_src.y);
   l_texcoordSW = c + float2(-texpix_src.x, -texpix_src.y);

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

@@ -100,8 +100,6 @@ from direct.showbase import DirectObject
 from direct.task import Task
 from direct.task.TaskManagerGlobal import taskMgr
 
-guiObjectCollector = PStatCollector("Client::GuiObjects")
-
 _track_gui_items = ConfigVariableBool('track-gui-items', False)
 
 
@@ -732,8 +730,6 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
         self.guiId = self.guiItem.getId()
 
         if ShowBaseGlobal.__dev__:
-            guiObjectCollector.addLevel(1)
-            guiObjectCollector.flushLevel()
             # track gui items by guiId for tracking down leaks
             if _track_gui_items:
                 if not hasattr(ShowBase, 'guiItems'):
@@ -1033,8 +1029,6 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     def destroy(self):
         if hasattr(self, "frameStyle"):
             if ShowBaseGlobal.__dev__:
-                guiObjectCollector.subLevel(1)
-                guiObjectCollector.flushLevel()
                 if hasattr(ShowBase, 'guiItems'):
                     ShowBase.guiItems.pop(self.guiId, None)
 

+ 2 - 1
direct/src/motiontrail/cMotionTrail.h

@@ -21,11 +21,12 @@
 #include "geomVertexWriter.h"
 #include "geomTriangles.h"
 #include "luse.h"
+#include "memoryBase.h"
 #include "nurbsCurveEvaluator.h"
 #include "plist.h"
 #include "epvector.h"
 
-class CMotionTrailVertex {
+class CMotionTrailVertex : public MemoryBase {
 public:
   LPoint4 _vertex;
   LVecBase4 _start_color;

+ 4 - 4
direct/src/showbase/BufferViewer.py

@@ -240,10 +240,10 @@ class BufferViewer(DirectObject):
             offsetx = (ringoffset[ring]*2.0) / float(sizex)
             offsety = (ringoffset[ring]*2.0) / float(sizey)
             bright = ringbright[ring]
-            vwriter.addData3f(-1-offsetx, 0, -1-offsety)
-            vwriter.addData3f(1+offsetx, 0, -1-offsety)
-            vwriter.addData3f(1+offsetx, 0,  1+offsety)
-            vwriter.addData3f(-1-offsetx, 0,  1+offsety)
+            vwriter.addData3f(Vec3.rfu(-1 - offsetx, 0, -1 - offsety))
+            vwriter.addData3f(Vec3.rfu( 1 + offsetx, 0, -1 - offsety))
+            vwriter.addData3f(Vec3.rfu( 1 + offsetx, 0,  1 + offsety))
+            vwriter.addData3f(Vec3.rfu(-1 - offsetx, 0,  1 + offsety))
             cwriter.addData3f(bright, bright, bright)
             cwriter.addData3f(bright, bright, bright)
             cwriter.addData3f(bright, bright, bright)

+ 2 - 2
direct/src/showbase/DirectObject.py

@@ -44,14 +44,14 @@ class DirectObject:
     def addTask(self, *args, **kwargs):
         if not hasattr(self, "_taskList"):
             self._taskList = {}
-        kwargs['owner']=self
+        kwargs['owner'] = self
         task = taskMgr.add(*args, **kwargs)
         return task
 
     def doMethodLater(self, *args, **kwargs):
         if not hasattr(self, "_taskList"):
             self._taskList = {}
-        kwargs['owner']=self
+        kwargs['owner'] = self
         task = taskMgr.doMethodLater(*args, **kwargs)
         return task
 

+ 1 - 1
direct/src/showbase/JobManager.py

@@ -1,4 +1,4 @@
-from panda3d.core import ConfigVariableBool, ConfigVariableDouble
+from panda3d.core import ConfigVariableBool, ConfigVariableDouble, ClockObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.Job import Job

+ 1 - 107
direct/src/showbase/PythonUtil.py

@@ -11,7 +11,7 @@ __all__ = [
     'boolEqual', 'lineupPos', 'formatElapsedSeconds', 'solveQuadratic',
     'findPythonModule', 'mostDerivedLast', 'clampScalar', 'weightedChoice',
     'randFloat', 'normalDistrib', 'weightedRand', 'randUint31', 'randInt32',
-    'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton',
+    'SerialNumGen', 'serialNum', 'uniqueName', 'Singleton',
     'SingletonError', 'printListEnum', 'safeRepr', 'fastRepr',
     'isDefaultValue', 'ScratchPad', 'Sync', 'itype', 'getNumberedTypedString',
     'getNumberedTypedSortedString', 'printNumberedTyped', 'DelayedCall',
@@ -1223,111 +1223,6 @@ def uniqueName(name):
     global _serialGen
     return '%s-%s' % (name, _serialGen.next())
 
-class EnumIter:
-    def __init__(self, enum):
-        self._values = tuple(enum._stringTable.keys())
-        self._index = 0
-    def __iter__(self):
-        return self
-    def __next__(self):
-        if self._index >= len(self._values):
-            raise StopIteration
-        self._index += 1
-        return self._values[self._index-1]
-
-class Enum:
-    """Pass in list of strings or string of comma-separated strings.
-    Items are accessible as instance.item, and are assigned unique,
-    increasing integer values. Pass in integer for 'start' to override
-    starting value.
-
-    Example:
-
-    >>> colors = Enum('red, green, blue')
-    >>> colors.red
-    0
-    >>> colors.green
-    1
-    >>> colors.blue
-    2
-    >>> colors.getString(colors.red)
-    'red'
-    """
-
-    if __debug__:
-        # chars that cannot appear within an item string.
-        def _checkValidIdentifier(item):
-            import string
-            invalidChars = string.whitespace + string.punctuation
-            invalidChars = invalidChars.replace('_', '')
-            invalidFirstChars = invalidChars+string.digits
-            if item[0] in invalidFirstChars:
-                raise SyntaxError("Enum '%s' contains invalid first char" %
-                                    item)
-            if not disjoint(item, invalidChars):
-                for char in item:
-                    if char in invalidChars:
-                        raise SyntaxError(
-                            "Enum\n'%s'\ncontains illegal char '%s'" %
-                            (item, char))
-            return 1
-        _checkValidIdentifier = staticmethod(_checkValidIdentifier)
-
-    def __init__(self, items, start=0):
-        if isinstance(items, str):
-            items = items.split(',')
-
-        self._stringTable = {}
-
-        # make sure we don't overwrite an existing element of the class
-        assert self._checkExistingMembers(items)
-        assert uniqueElements(items)
-
-        i = start
-        for item in items:
-            # remove leading/trailing whitespace
-            item = item.strip()
-            # is there anything left?
-            if len(item) == 0:
-                continue
-            # make sure there are no invalid characters
-            assert Enum._checkValidIdentifier(item)
-            self.__dict__[item] = i
-            self._stringTable[i] = item
-            i += 1
-
-    def __iter__(self):
-        return EnumIter(self)
-
-    def hasString(self, string):
-        return string in set(self._stringTable.values())
-
-    def fromString(self, string):
-        if self.hasString(string):
-            return self.__dict__[string]
-        # throw an error
-        {}[string]
-
-    def getString(self, value):
-        return self._stringTable[value]
-
-    def __contains__(self, value):
-        return value in self._stringTable
-
-    def __len__(self):
-        return len(self._stringTable)
-
-    def copyTo(self, obj):
-        # copies all members onto obj
-        for name, value in self._stringTable:
-            setattr(obj, name, value)
-
-    if __debug__:
-        def _checkExistingMembers(self, items):
-            for item in items:
-                if hasattr(self, item):
-                    return 0
-            return 1
 
 ############################################################
 # class: Singleton
@@ -2635,7 +2530,6 @@ class PriorityCallbacks:
 builtins.Functor = Functor
 builtins.Stack = Stack
 builtins.Queue = Queue
-builtins.Enum = Enum
 builtins.SerialNumGen = SerialNumGen
 builtins.SerialMaskedGen = SerialMaskedGen
 builtins.ScratchPad = ScratchPad

+ 8 - 2
direct/src/showbase/ShowBase.py

@@ -19,7 +19,9 @@ Built-in global variables
 Some key variables used in all Panda3D scripts are actually attributes of the
 ShowBase instance.  When creating an instance of this class, it will write many
 of these variables to the built-in scope of the Python interpreter, so that
-they are accessible to any Python module, without the need fors extra imports.
+they are accessible to any Python module, without the need for extra imports.
+For example, the ShowBase instance itself is accessible anywhere through the
+:data:`~builtins.base` variable.
 
 While these are handy for prototyping, we do not recommend using them in bigger
 projects, as it can make the code confusing to read to other Python developers,
@@ -1928,7 +1930,11 @@ class ShowBase(DirectObject.DirectObject):
         if port is None:
             port = -1
         PStatClient.connect(hostname, port)
-        return PStatClient.isConnected()
+        if PStatClient.isConnected():
+            PStatClient.mainTick()
+            return True
+        else:
+            return False
 
     def addSfxManager(self, extraSfxManager):
         """

+ 7 - 7
direct/src/stdpy/threading.py

@@ -216,8 +216,8 @@ class RLock(core.ReMutex):
         core.ReMutex.__init__(self, name)
 
 
-class Condition(core.ConditionVarFull):
-    """ This class provides a wrapper around Panda's ConditionVarFull
+class Condition(core.ConditionVar):
+    """ This class provides a wrapper around Panda's ConditionVar
     object.  The wrapper is designed to emulate Python's own
     threading.Condition object. """
 
@@ -230,7 +230,7 @@ class Condition(core.ConditionVarFull):
         assert isinstance(lock, Lock)
 
         self.__lock = lock
-        core.ConditionVarFull.__init__(self, self.__lock)
+        core.ConditionVar.__init__(self, self.__lock)
 
     def acquire(self, *args, **kw):
         return self.__lock.acquire(*args, **kw)
@@ -240,12 +240,12 @@ class Condition(core.ConditionVarFull):
 
     def wait(self, timeout = None):
         if timeout is None:
-            core.ConditionVarFull.wait(self)
+            core.ConditionVar.wait(self)
         else:
-            core.ConditionVarFull.wait(self, timeout)
+            core.ConditionVar.wait(self, timeout)
 
     def notifyAll(self):
-        core.ConditionVarFull.notifyAll(self)
+        core.ConditionVar.notifyAll(self)
 
     notify_all = notifyAll
 
@@ -295,7 +295,7 @@ class Event:
 
     def __init__(self):
         self.__lock = core.Mutex("Python Event")
-        self.__cvar = core.ConditionVarFull(self.__lock)
+        self.__cvar = core.ConditionVar(self.__lock)
         self.__flag = False
 
     def is_set(self):

+ 26 - 15
direct/src/task/Task.py

@@ -16,11 +16,17 @@ from direct.showbase.MessengerGlobal import messenger
 import types
 import random
 import importlib
+import sys
 
-try:
-    import _signal as signal
-except ImportError:
+# On Android, there's no use handling SIGINT, and in fact we can't, since we
+# run the application in a separate thread from the main thread.
+if hasattr(sys, 'getandroidapilevel'):
     signal = None
+else:
+    try:
+        import _signal as signal
+    except ImportError:
+        signal = None
 
 from panda3d.core import *
 from direct.extensions_native import HTTPChannel_extensions
@@ -31,7 +37,6 @@ def print_exc_plus():
     Print the usual traceback information, followed by a listing of all the
     local variables in each frame.
     """
-    import sys
     import traceback
 
     tb = sys.exc_info()[2]
@@ -127,6 +132,8 @@ class TaskManager:
         self.destroyed = False
         self.fKeyboardInterrupt = False
         self.interruptCount = 0
+        if signal:
+            self.__prevHandler = signal.default_int_handler
 
         self._frameProfileQueue = []
 
@@ -168,7 +175,7 @@ class TaskManager:
         print('*** allowing mid-frame keyboard interrupt.')
         # Restore default interrupt handler
         if signal:
-            signal.signal(signal.SIGINT, signal.default_int_handler)
+            signal.signal(signal.SIGINT, self.__prevHandler)
         # and invoke it
         raise KeyboardInterrupt
 
@@ -475,25 +482,30 @@ class TaskManager:
         chains that are in sub-threads or that have frame budgets
         might execute their tasks differently. """
 
+        startFrameTime = self.globalClock.getRealTime()
+
         # Replace keyboard interrupt handler during task list processing
         # so we catch the keyboard interrupt but don't handle it until
         # after task list processing is complete.
         self.fKeyboardInterrupt = 0
         self.interruptCount = 0
+
         if signal:
-            signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
+            self.__prevHandler = signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
 
-        startFrameTime = self.globalClock.getRealTime()
+        try:
+            self.mgr.poll()
 
-        self.mgr.poll()
+            # This is the spot for an internal yield function
+            nextTaskTime = self.mgr.getNextWakeTime()
+            self.doYield(startFrameTime, nextTaskTime)
 
-        # This is the spot for an internal yield function
-        nextTaskTime = self.mgr.getNextWakeTime()
-        self.doYield(startFrameTime, nextTaskTime)
+        finally:
+            # Restore previous interrupt handler
+            if signal:
+                signal.signal(signal.SIGINT, self.__prevHandler)
+                self.__prevHandler = signal.default_int_handler
 
-        # Restore default interrupt handler
-        if signal:
-            signal.signal(signal.SIGINT, signal.default_int_handler)
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
 
@@ -1264,7 +1276,6 @@ class TaskManager:
 
 if __debug__:
     def checkLeak():
-        import sys
         import gc
         gc.enable()
         from direct.showbase.DirectObject import DirectObject

+ 1 - 1
doc/INSTALL

@@ -185,7 +185,7 @@ it will show you the available command-line options:
   --use-opencv      --no-opencv    (enable/disable use of OPENCV)
   --use-directcam   --no-directcam (enable/disable use of DIRECTCAM)
   --use-vision      --no-vision    (enable/disable use of VISION)
-  --use-gtk2        --no-gtk2      (enable/disable use of GTK2)
+  --use-gtk3        --no-gtk3      (enable/disable use of GTK3)
   --use-npapi       --no-npapi     (enable/disable use of NPAPI)
   --use-mfc         --no-mfc       (enable/disable use of MFC)
   --use-wx          --no-wx        (enable/disable use of WX)

+ 60 - 0
doc/ReleaseNotes

@@ -1,3 +1,63 @@
+-----------------------  RELEASE 1.10.11  -----------------------
+
+Maintenance release containing assorted bug fixes and minor improvements.
+
+Rendering
+* Fix erratic shadow bug with multiple lights from gltf/blend2bam (#1153)
+* Fix erratic behavior of HW skinning shaders on non-animated models (#1207)
+* Fix errors with compressed luminance textures in DirectX 9 (#1198)
+* Implement screenshotting multisample backbuffer in DirectX 9 (#1225)
+
+Texture Loading
+* Don't load texture from disk when loading .bam if preloading is off (#1208)
+* Fix TextureReloadRequest not working properly when mipmapping is disabled
+* Add TexturePool.get_texture() method for querying textures in pool
+* Fix crash when opening a .txo, .dds or .ktx file fails
+* Improve error message when calling tex.write() with unknown extension
+
+Input
+* Generate horizontal scroll wheel events on Windows
+* Generate events for mouse buttons 4 and 5 on X11
+* Generate events for lmeta, rmeta and menu keys on Windows
+* Add raw event (raw-<) for key between shift and Z on ISO keyboards
+* Gracefully handle invalid raw input device data on Windows
+* Correctly handle negative axis input from Windows raw input devices (#1218)
+* FrSky RC controller is now registered as flight stick (#1218)
+
+Deployment
+* Support building with tkinter on all supported platforms (#780)
+* Fix issue with zipimport module not being packaged
+* Fix grayscale icons becoming blue when scaled automatically
+* Automatically include cacert.pem when depending on certifi
+* Suppress assorted spurious missing module warnings
+* Targeting linux_x86_64 / linux_i686 also allows use of manylinux wheels
+
+Build
+* Add support for Maya 2022 (#1213)
+* Support building with Visual Studio 2022
+* Support building with macOS 11.3 SDK (and work around clang crash)
+* Support building with Windows 11 SDK
+* Build Ubuntu .deb files with bindings for multiple Python 3 versions
+* Support compilation with Assimp 5.x (#1212)
+* Support building on manylinux_2_24
+
+Miscellaneous
+* Fix nodes with same tag key but different value getting flattened together
+* taskMgr.step() now restores previous SIGINT handler afterwards (#1180)
+* Add base.clock as alias for globalClock
+* FilterManager mul parameter now accepts floating-point values (#1231)
+* Assorted minor API documentation improvements
+* Fix memory leak getting Bullet persistent manifolds from Python (#1193)
+* Fix assertion in PythonLoaderFileType with debug Python build
+* Add missing property interface to PlaneNode
+* Fix prepare_scene() not properly invoking the Shader Generator
+* Add name property to AICharacter class (#1205)
+* Add bullet-split-impulse configuration variable (#1201)
+* Fix slider thumb entering dragging state on keyboard button press (#1188)
+* Allow OnscreenImage to be created before ShowBase is created (#1209)
+* Fix manager, t, play_rate, duration properties of Sequence/Parallel (#1202)
+* Expose ButtonEvent API to Python (UNSTABLE API, will be changed soon)
+
 -----------------------  RELEASE 1.10.10  -----------------------
 
 This release fixes assorted, mostly very minor bugs.

+ 12 - 32
dtool/CompilerFlags.cmake

@@ -48,21 +48,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GCC")
 
 endif()
 
-# Panda3D is now a C++11 project. Newer versions of CMake support this out of
-# the box; for older versions we take a shot in the dark:
-if(CMAKE_VERSION VERSION_LESS "3.1")
-  check_cxx_compiler_flag("-std=gnu++11" COMPILER_SUPPORTS_CXX11)
-  if(COMPILER_SUPPORTS_CXX11)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
-  else()
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
-  endif()
-
-else()
-  set(CMAKE_CXX_STANDARD 11)
-  set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-endif()
+# Panda3D is now a C++11 project.
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 # Set certain CMake flags we expect
 set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
@@ -110,21 +98,6 @@ if(APPLE)
   set(CMAKE_SHARED_MODULE_SUFFIX ".dylib")
 endif()
 
-# We want the output structured like build/CONFIG/bin, not build/bin/CONFIG per
-# the default for multi-configuration generators. In CMake 3.4+, it switches
-# automatically if the *_OUTPUT_DIRECTORY property contains a generator
-# expresssion, but as of this writing we support as early as CMake 3.0.2.
-#
-# So, let's just do this:
-if(CMAKE_VERSION VERSION_LESS "3.4")
-  foreach(_type RUNTIME ARCHIVE LIBRARY)
-    foreach(_config ${CMAKE_CONFIGURATION_TYPES})
-      string(TOUPPER "${_config}" _config)
-      set(CMAKE_${_type}_OUTPUT_DIRECTORY_${_config} "${CMAKE_${_type}_OUTPUT_DIRECTORY}")
-    endforeach(_config)
-  endforeach(_type)
-endif()
-
 # Set warning levels
 if(MSVC)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3")
@@ -138,7 +111,7 @@ endif()
 
 if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
   set(global_flags
-    "-Wno-unused-function -Wno-unused-parameter -fno-strict-aliasing")
+    "-Wno-unused-function -Wno-unused-parameter -fno-strict-aliasing -Werror=return-type")
   set(release_flags "-Wno-unused-variable")
 
   if(NOT MSVC)
@@ -154,7 +127,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
   endif()
 
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${global_flags}")
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${global_flags} -Wno-reorder")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${global_flags}")
   set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${release_flags}")
   set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${release_flags}")
   set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${release_flags}")
@@ -245,3 +218,10 @@ if(NOT MSVC)
     add_compile_options("-fvisibility=hidden")
   endif()
 endif()
+
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  check_cxx_compiler_flag("-fno-semantic-interposition" COMPILER_SUPPORTS_FNO_SEMANTIC_INTERPOSITION)
+  if(COMPILER_SUPPORTS_FNO_SEMANTIC_INTERPOSITION)
+    add_compile_options("-fno-semantic-interposition")
+  endif()
+endif()

+ 42 - 5
dtool/Config.cmake

@@ -288,6 +288,26 @@ mark_as_advanced(SIMULATE_NETWORK_DELAY DO_MEMORY_USAGE DO_DCAST)
 # The following options have to do with the memory allocation system.
 #
 
+find_package(MIMALLOC 1.0 QUIET)
+
+package_option(MIMALLOC
+  "The mimalloc allocator.  See also USE_MEMORY_MIMALLOC, which
+you will need to use to activate it by default.  If you do not set
+USE_MEMORY_MIMALLOC, Panda will decide whether to use it."
+  IMPORTED_AS mimalloc-static)
+
+if (WIN32 AND HAVE_MIMALLOC)
+  set(_prefer_mimalloc ON)
+else()
+  set(_prefer_mimalloc OFF)
+endif()
+
+option(USE_MEMORY_MIMALLOC
+  "This is an optional memory allocator with good multi-threading
+support.  It is recommended on Windows, where it gives much better
+performance than the built-in malloc.  However, it does not appear
+to be significantly faster on glibc-based systems." ${_prefer_mimalloc})
+
 option(USE_MEMORY_DLMALLOC
   "This is an optional alternative memory-allocation scheme
 available within Panda.  You can experiment with it to see
@@ -307,16 +327,33 @@ if 16-byte alignment must be performed on top of it, wasting up to
 is required and not provided by the system malloc library, then an
 alternative malloc system (above) will be used instead." OFF)
 
-option(USE_DELETED_CHAIN
-  "Define this true to use the DELETED_CHAIN macros, which support
+if (WIN32 AND NOT HAVE_MIMALLOC)
+  option(USE_DELETED_CHAIN
+    "Define this true to use the DELETED_CHAIN macros, which support
 fast re-use of existing allocated blocks, minimizing the low-level
 calls to malloc() and free() for frequently-created and -deleted
-objects.  There's usually no reason to set this false, unless you
-suspect a bug in Panda's memory management code." ON)
+objects.  This is significantly better than built-in malloc on Windows
+but suffers with multiple threads, where mimalloc performs better, so
+it is preferred to get mimalloc instead and turn this OFF." ON)
+else()
+  option(USE_DELETED_CHAIN
+    "Define this true to use the DELETED_CHAIN macros, which support
+fast re-use of existing allocated blocks, minimizing the low-level
+calls to malloc() and free() for frequently-created and -deleted
+objects.  However, modern memory allocators generally perform as good,
+especially with threading, so best leave this OFF." OFF)
+endif()
 
 mark_as_advanced(USE_MEMORY_DLMALLOC USE_MEMORY_PTMALLOC2
-  MEMORY_HOOK_DO_ALIGN USE_DELETED_CHAIN)
+  USE_MEMORY_MIMALLOC MEMORY_HOOK_DO_ALIGN USE_DELETED_CHAIN)
+
+if(USE_MEMORY_MIMALLOC)
+  package_status(MIMALLOC "mimalloc memory allocator")
+else()
+  package_status(MIMALLOC "mimalloc memory allocator (not used)")
+endif()
 
+unset(_prefer_mimalloc)
 
 #
 # This section relates to mobile-device/phone support and options

+ 41 - 24
dtool/Package.cmake

@@ -1,5 +1,5 @@
 set(_thirdparty_dir_default "${PROJECT_SOURCE_DIR}/thirdparty")
-if(NOT (APPLE OR WIN32) OR NOT IS_DIRECTORY "${_thirdparty_dir_default}")
+if(NOT IS_DIRECTORY "${_thirdparty_dir_default}")
   set(_thirdparty_dir_default "")
 endif()
 
@@ -47,6 +47,27 @@ if(THIRDPARTY_DIRECTORY)
     set(BISON_ROOT "${THIRDPARTY_DIRECTORY}/win-util")
     set(FLEX_ROOT "${THIRDPARTY_DIRECTORY}/win-util")
 
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/linux-libs-arm64)
+    elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/linux-libs-x64)
+    else()
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/linux-libs-a)
+    endif()
+
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+    if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/freebsd-libs-arm64)
+    elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/freebsd-libs-x64)
+    else()
+      set(_package_dir ${THIRDPARTY_DIRECTORY}/freebsd-libs-a)
+    endif()
+
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Android")
+    set(_package_dir ${THIRDPARTY_DIRECTORY}/android-libs-${CMAKE_ANDROID_ARCH})
+
   else()
     message(FATAL_ERROR
       "You can't use THIRDPARTY_DIRECTORY on this platform. Unset it to continue.")
@@ -187,19 +208,6 @@ if(Python_FOUND)
   set(PYTHON_INCLUDE_DIRS ${Python_INCLUDE_DIRS})
   set(PYTHON_LIBRARY_DIRS ${Python_LIBRARY_DIRS})
   set(PYTHON_VERSION_STRING ${Python_VERSION})
-
-elseif(CMAKE_VERSION VERSION_LESS "3.12")
-  find_package(PythonInterp ${WANT_PYTHON_VERSION} QUIET)
-  find_package(PythonLibs ${PYTHON_VERSION_STRING} QUIET)
-
-  if(PYTHONLIBS_FOUND)
-    set(PYTHON_FOUND ON)
-
-    if(NOT PYTHON_VERSION_STRING)
-      set(PYTHON_VERSION_STRING ${PYTHONLIBS_VERSION_STRING})
-    endif()
-  endif()
-
 endif()
 
 if(CMAKE_VERSION VERSION_LESS "3.15")
@@ -366,14 +374,22 @@ package_option(TIFF "Enable support for loading .tif images.")
 package_status(TIFF "libtiff")
 
 # OpenEXR
-find_package(OpenEXR QUIET MODULE)
+find_package(OpenEXR QUIET)
 
-package_option(OpenEXR "Enable support for loading .exr images.")
+if (TARGET OpenEXR::IlmImf AND NOT TARGET OpenEXR::OpenEXR)
+  package_option(OpenEXR
+    "Enable support for loading .exr images."
+    IMPORTED_AS OpenEXR::IlmImf)
+else()
+  package_option(OpenEXR
+    "Enable support for loading .exr images."
+    IMPORTED_AS OpenEXR::OpenEXR)
+endif()
 
 package_status(OpenEXR "OpenEXR")
 
 # libsquish
-find_package(LibSquish QUIET)
+find_package(LibSquish QUIET MODULE)
 
 package_option(SQUISH
   "Enables support for automatic compression of DXT textures."
@@ -553,16 +569,17 @@ package_option(HarfBuzz
 
 package_status(HarfBuzz "HarfBuzz")
 
-# GTK2
+# GTK3
 
-set(Freetype_FIND_QUIETLY TRUE) # Fix for builtin FindGTK2
-set(GTK2_GTK_FIND_QUIETLY TRUE) # Fix for builtin FindGTK2
-find_package(GTK2 QUIET COMPONENTS gtk)
-
-package_option(GTK2)
+if(NOT WIN32)
+  find_package(GTK3 QUIET)
+endif()
 
-package_status(GTK2 "gtk+-2")
+package_option(GTK3
+  "This is necessary to build the PStats performance analysis tool on platforms
+  other than Windows.")
 
+package_status(GTK3 "gtk+-3")
 
 #
 # ------------ Physics engines ------------

+ 0 - 4
dtool/PandaVersion.cmake

@@ -46,10 +46,6 @@ math(EXPR PANDA_NUMERIC_VERSION "${PROJECT_VERSION_MAJOR}*1000000 + ${PROJECT_VE
 
 # If SOURCE_DATE_EPOCH is set, it affects PandaSystem::get_build_date()
 if(DEFINED ENV{SOURCE_DATE_EPOCH})
-  if(CMAKE_VERSION VERSION_LESS "3.8")
-    message(FATAL_ERROR "CMake 3.8 is required to support SOURCE_DATE_EPOCH properly.")
-  endif()
-
   string(TIMESTAMP _build_date "%b %d %Y %H:%M:%S" UTC)
 
   # CMake doesn't support %e, replace leading zero in day with space

+ 5 - 1
dtool/dtool_config.h.in

@@ -125,8 +125,12 @@
 /* Define if we want to support fixed-function OpenGL rendering. */
 #cmakedefine SUPPORT_FIXED_FUNCTION
 
-/* Define for either of the alternative malloc schemes. */
+/* Define if we have mimalloc available. */
+#cmakedefine HAVE_MIMALLOC
+
+/* Define for one of the alternative malloc schemes. */
 #cmakedefine USE_MEMORY_DLMALLOC
+#cmakedefine USE_MEMORY_MIMALLOC
 #cmakedefine USE_MEMORY_PTMALLOC2
 
 /* Define if we want to compile in support for pipelining.  */

Разница между файлами не показана из-за своего большого размера
+ 211 - 93
dtool/metalibs/dtoolconfig/pydtool.cxx


+ 2 - 2
dtool/src/cppparser/cppManifest.cxx

@@ -226,8 +226,8 @@ output(std::ostream &out) const {
         out << "$1";
       }
 
-      for (int i = 1; i < _num_parameters; ++i) {
-        if (_variadic_param == i) {
+      for (size_t i = 1; i < _num_parameters; ++i) {
+        if (_variadic_param == (int)i) {
           out << ", ...";
         } else {
           out << ", $" << i + 1;

+ 1 - 1
dtool/src/cppparser/cppManifest.h

@@ -43,7 +43,7 @@ public:
 
   std::string _name;
   bool _has_parameters;
-  int _num_parameters;
+  size_t _num_parameters;
   int _variadic_param;
   cppyyltype _loc;
   CPPExpression *_expr;

+ 2 - 2
dtool/src/cppparser/cppNamespace.cxx

@@ -22,9 +22,9 @@
 CPPNamespace::
 CPPNamespace(CPPIdentifier *ident, CPPScope *scope, const CPPFile &file) :
   CPPDeclaration(file),
+  _is_inline(false),
   _ident(ident),
-  _scope(scope),
-  _is_inline(false)
+  _scope(scope)
 {
 }
 

+ 2 - 2
dtool/src/cppparser/cppStructType.cxx

@@ -58,8 +58,8 @@ CPPStructType(const CPPStructType &copy) :
   CPPExtensionType(copy),
   _scope(copy._scope),
   _incomplete(copy._incomplete),
-  _derivation(copy._derivation),
-  _final(copy._final)
+  _final(copy._final),
+  _derivation(copy._derivation)
 {
   _subst_decl_recursive_protect = false;
 }

+ 8 - 1
dtool/src/dtoolbase/CMakeLists.txt

@@ -46,6 +46,7 @@ set(P3DTOOLBASE_HEADERS
   typeRegistryNode.I typeRegistryNode.h
   typedObject.I typedObject.h
   pallocator.T pallocator.h
+  patomic.h patomic.I
   pdeque.h plist.h pmap.h pset.h
   pvector.h epvector.h
   lookup3.h
@@ -70,6 +71,7 @@ set(P3DTOOLBASE_SOURCES
   mutexWin32Impl.cxx
   mutexSpinlockImpl.cxx
   neverFreeMemory.cxx
+  patomic.cxx
   pdtoa.cxx
   pstrtod.cxx
   register_type.cxx
@@ -92,9 +94,14 @@ add_component_library(p3dtoolbase NOINIT SYMBOL BUILDING_DTOOL_DTOOLBASE
 target_include_directories(p3dtoolbase PUBLIC
   $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
   $<BUILD_INTERFACE:${PANDA_OUTPUT_DIR}/include>)
-target_link_libraries(p3dtoolbase PKG::EIGEN PKG::THREADS)
+target_link_libraries(p3dtoolbase PKG::EIGEN PKG::THREADS PKG::MIMALLOC)
 target_interrogate(p3dtoolbase ${P3DTOOLBASE_SOURCES} EXTENSIONS ${P3DTOOLBASE_IGATEEXT})
 
+if(HAVE_MIMALLOC AND CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
+  # Do not re-export symbols from these libraries.
+  target_link_options(p3dtoolbase PRIVATE "LINKER:--exclude-libs,libmimalloc.a")
+endif()
+
 if(NOT BUILD_METALIBS)
   install(TARGETS p3dtoolbase
     EXPORT Core COMPONENT Core

+ 1 - 1
dtool/src/dtoolbase/deletedBufferChain.I

@@ -27,7 +27,7 @@ validate(void *ptr) {
 
 #if defined(USE_DELETEDCHAINFLAG) && defined(USE_DELETED_CHAIN)
   const ObjectNode *obj = buffer_to_node(ptr);
-  return AtomicAdjust::get(obj->_flag) == DCF_alive;
+  return obj->_flag.load(std::memory_order_relaxed) == DCF_alive;
 #else
   return true;
 #endif  // USE_DELETEDCHAINFLAG

+ 13 - 11
dtool/src/dtoolbase/deletedBufferChain.cxx

@@ -50,8 +50,8 @@ allocate(size_t size, TypeHandle type_handle) {
     _lock.unlock();
 
 #ifdef USE_DELETEDCHAINFLAG
-    assert(obj->_flag == (AtomicAdjust::Integer)DCF_deleted);
-    obj->_flag = DCF_alive;
+    DeletedChainFlag orig_flag = obj->_flag.exchange(DCF_alive, std::memory_order_relaxed);
+    assert(orig_flag == DCF_deleted);
 #endif  // USE_DELETEDCHAINFLAG
 
     void *ptr = node_to_buffer(obj);
@@ -75,7 +75,7 @@ allocate(size_t size, TypeHandle type_handle) {
   obj = (ObjectNode *)(aligned - flag_reserved_bytes);
 
 #ifdef USE_DELETEDCHAINFLAG
-  obj->_flag = DCF_alive;
+  obj->_flag.store(DCF_alive, std::memory_order_relaxed);
 #endif  // USE_DELETEDCHAINFLAG
 
   void *ptr = node_to_buffer(obj);
@@ -116,14 +116,16 @@ deallocate(void *ptr, TypeHandle type_handle) {
   ObjectNode *obj = buffer_to_node(ptr);
 
 #ifdef USE_DELETEDCHAINFLAG
-  AtomicAdjust::Integer orig_flag = AtomicAdjust::compare_and_exchange(obj->_flag, DCF_alive, DCF_deleted);
-
-  // If this assertion is triggered, you double-deleted an object.
-  assert(orig_flag != (AtomicAdjust::Integer)DCF_deleted);
-
-  // If this assertion is triggered, you tried to delete an object that was
-  // never allocated, or you have heap corruption.
-  assert(orig_flag == (AtomicAdjust::Integer)DCF_alive);
+  DeletedChainFlag orig_flag = DCF_alive;
+  if (UNLIKELY(!obj->_flag.compare_exchange_strong(orig_flag, DCF_deleted,
+                                                   std::memory_order_relaxed))) {
+    // If this assertion is triggered, you double-deleted an object.
+    assert(orig_flag != DCF_deleted);
+
+    // If this assertion is triggered, you tried to delete an object that was
+    // never allocated, or you have heap corruption.
+    assert(orig_flag == DCF_alive);
+  }
 #endif  // USE_DELETEDCHAINFLAG
 
   _lock.lock();

+ 3 - 2
dtool/src/dtoolbase/deletedBufferChain.h

@@ -20,6 +20,7 @@
 #include "atomicAdjust.h"
 #include "numeric_types.h"
 #include "typeHandle.h"
+#include "patomic.h"
 #include <assert.h>
 
 // Though it's tempting, it doesn't seem to be possible to implement
@@ -37,7 +38,7 @@
 #endif // NDEBUG
 
 #ifdef USE_DELETEDCHAINFLAG
-enum DeletedChainFlag {
+enum DeletedChainFlag : unsigned int {
   DCF_deleted = 0xfeedba0f,
   DCF_alive = 0x12487654,
 };
@@ -73,7 +74,7 @@ private:
     // In development mode, we piggyback this extra data.  This is maintained
     // out-of-band from the actual pointer returned, so we can safely use this
     // flag to indicate the difference between allocated and freed pointers.
-    TVOLATILE AtomicAdjust::Integer _flag;
+    patomic<DeletedChainFlag> _flag;
 #endif
 
     // This pointer sits within the buffer, in the same space referenced by

+ 11 - 1
dtool/src/dtoolbase/dtoolbase.h

@@ -375,6 +375,10 @@ typedef struct _object PyObject;
 // This specialized malloc implementation can perform the required alignment.
 #undef MEMORY_HOOK_DO_ALIGN
 
+#elif defined(USE_MEMORY_MIMALLOC)
+// This one does, too.
+#undef MEMORY_HOOK_DO_ALIGN
+
 #elif defined(USE_MEMORY_PTMALLOC2)
 // But not this one.  For some reason it crashes when we try to build it with
 // alignment 16.  So if we're using ptmalloc2, we need to enforce alignment
@@ -385,6 +389,12 @@ typedef struct _object PyObject;
 // The OS-provided malloc implementation will do the required alignment.
 #undef MEMORY_HOOK_DO_ALIGN
 
+#elif defined(HAVE_MIMALLOC) && defined(_WIN32)
+// Prefer mimalloc on Windows, if we have it.  It is significantly faster than
+// standard malloc, supports multi-threading well and does the alignment too.
+#undef MEMORY_HOOK_DO_ALIGN
+#define USE_MEMORY_MIMALLOC 1
+
 #elif defined(MEMORY_HOOK_DO_ALIGN)
 // We need memory alignment, and we're willing to provide it ourselves.
 
@@ -426,7 +436,7 @@ typedef struct _object PyObject;
 #endif
 
 /* Determine our memory-allocation requirements. */
-#if defined(USE_MEMORY_PTMALLOC2) || defined(USE_MEMORY_DLMALLOC) || defined(DO_MEMORY_USAGE) || defined(MEMORY_HOOK_DO_ALIGN)
+#if defined(USE_MEMORY_MIMALLOC) || defined(USE_MEMORY_PTMALLOC2) || defined(USE_MEMORY_DLMALLOC) || defined(DO_MEMORY_USAGE) || defined(MEMORY_HOOK_DO_ALIGN)
 /* In this case we have some custom memory management requirements. */
 #else
 /* Otherwise, if we have no custom memory management needs at all, we

+ 0 - 5
dtool/src/dtoolbase/dtoolbase_cc.h

@@ -49,8 +49,6 @@
 // interrogate pass (CPPPARSER isn't defined), this maps to public.
 #define PUBLISHED __published
 
-#define PHAVE_ATOMIC 1
-
 typedef int ios_openmode;
 typedef int ios_fmtflags;
 typedef int ios_iostate;
@@ -112,9 +110,6 @@ typedef std::ios::seekdir ios_seekdir;
 #define INLINE inline
 #endif
 
-// Expect that we have access to the <atomic> header.
-#define PHAVE_ATOMIC 1
-
 // Determine the availability of C++11 features.
 #if defined(_MSC_VER) && _MSC_VER < 1900 // Visual Studio 2015
 #error Microsoft Visual C++ 2015 or later is required to compile Panda3D.

+ 12 - 0
dtool/src/dtoolbase/memoryHook.cxx

@@ -51,6 +51,18 @@ static_assert((MEMORY_HOOK_ALIGNMENT & (MEMORY_HOOK_ALIGNMENT - 1)) == 0,
 
 #if defined(CPPPARSER)
 
+#elif defined(USE_MEMORY_MIMALLOC)
+
+// mimalloc is a modern memory manager by Microsoft that is very fast as well
+// as thread-safe.
+
+#include "mimalloc.h"
+
+#define call_malloc mi_malloc
+#define call_realloc mi_realloc
+#define call_free mi_free
+#undef MEMORY_HOOK_MALLOC_LOCK
+
 #elif defined(USE_MEMORY_DLMALLOC)
 
 // Memory manager: DLMALLOC This is Doug Lea's memory manager.  It is very

+ 0 - 2
dtool/src/dtoolbase/mutexSpinlockImpl.h

@@ -19,9 +19,7 @@
 
 #ifdef MUTEX_SPINLOCK
 
-#ifdef PHAVE_ATOMIC
 #include <atomic>
-#endif
 
 /**
  * Uses a simple user-space spinlock to implement a mutex.  It is usually not

+ 1 - 1
dtool/src/dtoolbase/mutexWin32Impl.cxx

@@ -13,7 +13,7 @@
 
 #include "selectThreadImpl.h"
 
-#if defined(_WIN32) && !defined(CPPPARSER)
+#ifdef _WIN32
 
 #include "mutexWin32Impl.h"
 

+ 1 - 0
dtool/src/dtoolbase/p3dtoolbase_composite2.cxx

@@ -1,3 +1,4 @@
+#include "patomic.cxx"
 #include "mutexPosixImpl.cxx"
 #include "mutexWin32Impl.cxx"
 #include "mutexSpinlockImpl.cxx"

+ 472 - 0
dtool/src/dtoolbase/patomic.I

@@ -0,0 +1,472 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file patomic.I
+ * @author rdb
+ * @date 2022-01-28
+ */
+
+#if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
+/**
+ * Value initializer.
+ */
+template<class T>
+constexpr patomic<T>::
+patomic(T desired) noexcept : _value(desired) {
+}
+
+/**
+ * Returns true if this is a lock free type (which it always is).
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+is_lock_free() const noexcept {
+  return true;
+}
+
+/**
+ * Returns the stored value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+load(std::memory_order order) const noexcept {
+  return _value;
+}
+
+/**
+ * Returns the stored value.
+ */
+template<class T>
+ALWAYS_INLINE patomic<T>::
+operator T() const noexcept {
+  return _value;
+}
+
+/**
+ * Changes the stored value.
+ */
+template<class T>
+ALWAYS_INLINE void patomic<T>::
+store(T desired, std::memory_order order) noexcept {
+  _value = desired;
+}
+
+/**
+ * Changes the stored value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator=(T desired) noexcept {
+  _value = desired;
+}
+
+/**
+ * Changes the stored value, returning the previous value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+exchange(T desired, std::memory_order) noexcept {
+  T current = _value;
+  _value = desired;
+  return current;
+}
+
+/**
+ * Sets the desired value if the current value is as the first argument.
+ * If it is not, the current value is written to expected.
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+compare_exchange_weak(T &expected, T desired,
+                      std::memory_order, std::memory_order) noexcept {
+  T current = _value;
+  if (_value == expected) {
+    _value = desired;
+    return true;
+  } else {
+    expected = current;
+    return false;
+  }
+}
+
+/**
+ * Sets the desired value if the current value is as the first argument.
+ * If it is not, the current value is written to expected.
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+compare_exchange_strong(T &expected, T desired,
+                        std::memory_order, std::memory_order) noexcept {
+  T current = _value;
+  if (_value == expected) {
+    _value = desired;
+    return true;
+  } else {
+    expected = current;
+    return false;
+  }
+}
+
+/**
+ * Adds to the stored value, returns the old value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_add(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value += arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_sub(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value -= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_and(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value &= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_or(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value |= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_xor(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value ^= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ++(int) noexcept {
+  return _value++;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator --(int) noexcept {
+  return _value--;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ++() noexcept {
+  return ++_value;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator --() noexcept {
+  return --_value;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator +=(T arg) noexcept {
+  return _value += arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator -=(T arg) noexcept {
+  return _value -= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator &=(T arg) noexcept {
+  return _value &= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator |=(T arg) noexcept {
+  return _value |= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ^=(T arg) noexcept {
+  return _value ^= arg;
+}
+
+#endif  // defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
+
+/**
+ * Initializes the variable to zero (according to C++20 semantics, NOT C++11
+ * semantics!)
+ */
+constexpr patomic_unsigned_lock_free::
+patomic_unsigned_lock_free() noexcept :
+  patomic<uint32_t>(0u) {
+}
+
+/**
+ * Initializes the variable to the given value.
+ */
+constexpr patomic_unsigned_lock_free::
+patomic_unsigned_lock_free(uint32_t desired) noexcept :
+  patomic<uint32_t>(desired) {
+}
+
+/**
+ * Waits until the value is no longer equal to the given value.
+ */
+ALWAYS_INLINE void patomic_unsigned_lock_free::
+wait(uint32_t old, std::memory_order order) const noexcept {
+  if (load(order) == old) {
+    patomic_wait((const volatile uint32_t *)this, old);
+  }
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_unsigned_lock_free::
+notify_one() noexcept {
+  patomic_notify_one((volatile uint32_t *)this);
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_unsigned_lock_free::
+notify_all() noexcept {
+  patomic_notify_all((volatile uint32_t *)this);
+}
+
+/**
+ * Initializes the variable to zero (according to C++20 semantics, NOT C++11
+ * semantics!)
+ */
+constexpr patomic_signed_lock_free::
+patomic_signed_lock_free() noexcept :
+  patomic<int32_t>(0) {
+}
+
+/**
+ * Initializes the variable to the given value.
+ */
+constexpr patomic_signed_lock_free::
+patomic_signed_lock_free(int32_t desired) noexcept :
+  patomic<int32_t>(desired) {
+}
+
+/**
+ * Waits until the value is no longer equal to the given value.
+ */
+ALWAYS_INLINE void patomic_signed_lock_free::
+wait(int32_t old, std::memory_order order) const noexcept {
+  if (load(order) == old) {
+    patomic_wait((const volatile int32_t *)this, old);
+  }
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_signed_lock_free::
+notify_one() noexcept {
+  patomic_notify_one((volatile int32_t *)this);
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_signed_lock_free::
+notify_all() noexcept {
+  patomic_notify_all((volatile int32_t *)this);
+}
+
+/**
+ * Allows assignment from ATOMIC_FLAG_INIT.
+ */
+constexpr patomic_flag::
+patomic_flag(bool desired) noexcept : _value(desired) {
+}
+
+/**
+ * Sets the flag to false.
+ */
+ALWAYS_INLINE void patomic_flag::
+clear(std::memory_order order) noexcept {
+  _value.store(0u, order);
+}
+
+/**
+ * Sets the flag to true and returns the previous value.
+ */
+ALWAYS_INLINE bool patomic_flag::
+test_and_set(std::memory_order order) noexcept {
+  return _value.exchange(1u, order) != 0u;
+}
+
+/**
+ * Returns the current value of the flag.
+ */
+ALWAYS_INLINE bool patomic_flag::
+test(std::memory_order order) const noexcept {
+  return _value.load(order) != 0u;
+}
+
+/**
+ * Waits until the value is no longer equal to the given value.
+ */
+ALWAYS_INLINE void patomic_flag::
+wait(bool old, std::memory_order order) const noexcept {
+  _value.wait(old, order);
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_flag::
+notify_one() noexcept {
+  _value.notify_one();
+}
+
+/**
+ * Wakes up at least one thread waiting for the value to change.
+ */
+ALWAYS_INLINE void patomic_flag::
+notify_all() noexcept {
+  _value.notify_all();
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_wait(const volatile int32_t *value, int32_t old) {
+  patomic_wait((const volatile uint32_t *)value, (uint32_t)old);
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_notify_one(volatile int32_t *value) {
+  patomic_notify_one((volatile uint32_t *)value);
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_notify_all(volatile int32_t *value) {
+  patomic_notify_all((volatile uint32_t *)value);
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_wait(const volatile uint32_t *value, uint32_t old) {
+#ifdef __linux__
+  while (__atomic_load_n(value, __ATOMIC_SEQ_CST) == old) {
+    syscall(SYS_futex, old, FUTEX_WAIT_PRIVATE, old, 0, 0, 0);
+  }
+//#elif _WIN32_WINNT >= _WIN32_WINNT_WIN8
+//  while (*value == old) {
+//    WaitOnAddress((volatile void *)value, &old, sizeof(uint32_t), INFINITE);
+//  }
+#elif defined(_WIN32)
+  while (*value == old) {
+    _patomic_wait_func((volatile void *)value, &old, sizeof(uint32_t), INFINITE);
+  }
+#elif defined(HAVE_POSIX_THREADS)
+  _patomic_wait(value, old);
+#else
+  while (*value == old);
+#endif
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_notify_one(volatile uint32_t *value) {
+#ifdef __linux__
+  syscall(SYS_futex, value, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0);
+//#elif _WIN32_WINNT >= _WIN32_WINNT_WIN8
+//  WakeByAddressSingle((void *)value);
+#elif defined(_WIN32)
+  _patomic_wake_one_func((void *)value);
+#elif defined(HAVE_POSIX_THREADS)
+  _patomic_notify_all(value);
+#endif
+}
+
+/**
+ *
+ */
+ALWAYS_INLINE void
+patomic_notify_all(volatile uint32_t *value) {
+#ifdef __linux__
+  syscall(SYS_futex, value, FUTEX_WAKE_PRIVATE, INT_MAX, 0, 0, 0);
+//#elif _WIN32_WINNT >= _WIN32_WINNT_WIN8
+//  WakeByAddressAll((void *)value);
+#elif defined(_WIN32)
+  _patomic_wake_all_func((void *)value);
+#elif defined(HAVE_POSIX_THREADS)
+  _patomic_notify_all(value);
+#endif
+}

+ 168 - 0
dtool/src/dtoolbase/patomic.cxx

@@ -0,0 +1,168 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file patomic.cxx
+ * @author rdb
+ * @date 2022-02-23
+ */
+
+#include "patomic.h"
+
+#include <functional>
+
+static_assert(sizeof(patomic_unsigned_lock_free) == sizeof(uint32_t),
+              "expected atomic uint32_t to have same size as uint32_t");
+static_assert(sizeof(patomic_signed_lock_free) == sizeof(int32_t),
+              "expected atomic int32_t to have same size as int32_t");
+static_assert(sizeof(uint32_t) == sizeof(int32_t),
+              "expected int32_t to have same size as uint32_t");
+
+#if !defined(CPPPARSER) && defined(_WIN32)
+
+// On Windows 7, we try to load the Windows 8 functions dynamically, and
+// fall back to a condition variable table if they aren't available.
+static BOOL __stdcall initialize_wait(volatile VOID *addr, PVOID cmp, SIZE_T size, DWORD timeout);
+static void __stdcall dummy_wake(PVOID addr) {}
+
+BOOL (__stdcall *_patomic_wait_func)(volatile VOID *, PVOID, SIZE_T, DWORD) = &initialize_wait;
+void (__stdcall *_patomic_wake_one_func)(PVOID) = &dummy_wake;
+void (__stdcall *_patomic_wake_all_func)(PVOID) = &dummy_wake;
+
+// Randomly pick an entry into the wait table based on the hash of the address.
+// It's possible to get hash collision, but that's not so bad, it just means
+// that the other thread will get a spurious wakeup.
+struct alignas(64) WaitTableEntry {
+  SRWLOCK _lock = SRWLOCK_INIT;
+  CONDITION_VARIABLE _cvar = CONDITION_VARIABLE_INIT;
+  DWORD _waiters = 0;
+};
+static WaitTableEntry _wait_table[64] = {};
+static const size_t _wait_hash_mask = 63;
+
+/**
+ * Emulates WakeByAddressSingle for Windows Vista and 7.
+ */
+static void __stdcall
+emulated_wake(PVOID addr) {
+  size_t i = std::hash<volatile void *>{}(addr) & (sizeof(_wait_table) / sizeof(WaitTableEntry) - 1);
+  WaitTableEntry &entry = _wait_table[i];
+  AcquireSRWLockExclusive(&entry._lock);
+  DWORD num_waiters = entry._waiters;
+  ReleaseSRWLockExclusive(&entry._lock);
+  if (num_waiters > 0) {
+    // We have to wake up all the threads, even if only one of them is for this
+    // address.  Some of them will get a spurious wakeup, but that's OK.
+    WakeAllConditionVariable(&entry._cvar);
+  }
+}
+
+/**
+ * Emulates WaitOnAddress for Windows Vista and 7.  Only supports aligned
+ * 32-bit values.
+ */
+static BOOL __stdcall
+emulated_wait(volatile VOID *addr, PVOID cmp, SIZE_T size, DWORD timeout) {
+  assert(size == sizeof(LONG));
+
+  LONG cmpval = *(LONG *)cmp;
+  if (*(LONG *)addr != cmpval) {
+    return TRUE;
+  }
+
+  size_t i = std::hash<volatile void *>{}(addr) & _wait_hash_mask;
+  WaitTableEntry &entry = _wait_table[i];
+  AcquireSRWLockExclusive(&entry._lock);
+  ++entry._waiters;
+  while (*(LONG *)addr == cmpval) {
+    if (SleepConditionVariableSRW(&entry._cvar, &entry._lock, timeout, 0) != 0) {
+      // Timeout.
+      --entry._waiters;
+      ReleaseSRWLockExclusive(&entry._lock);
+      return FALSE;
+    }
+  }
+  --entry._waiters;
+  ReleaseSRWLockExclusive(&entry._lock);
+  return TRUE;
+}
+
+/**
+ * Initially assigned to the wait function slot to initialize the function
+ * pointers.
+ */
+static BOOL __stdcall
+initialize_wait(volatile VOID *addr, PVOID cmp, SIZE_T size, DWORD timeout) {
+  // There's a chance of a race here, with two threads trying to initialize the
+  // functions at the same time.  That's OK, because they should all produce
+  // the same results, and the stores to the function pointers are atomic.
+  HMODULE lib = GetModuleHandleW(L"api-ms-win-core-synch-l1-2-0.dll");
+  if (lib) {
+    auto wait_func = (decltype(_patomic_wait_func))GetProcAddress(lib, "WaitOnAddress");
+    auto wake_one_func = (decltype(_patomic_wake_one_func))GetProcAddress(lib, "WakeByAddressSingle");
+    auto wake_all_func = (decltype(_patomic_wake_all_func))GetProcAddress(lib, "WakeByAddressAll");
+    if (wait_func && wake_one_func && wake_all_func) {
+      // Make sure that the wake function is guaranteed to be visible to other
+      // threads by the time we assign the wait function.
+      _patomic_wake_one_func = wake_one_func;
+      _patomic_wake_all_func = wake_all_func;
+      patomic_thread_fence(std::memory_order_release);
+      _patomic_wait_func = wait_func;
+      return wait_func(addr, cmp, size, timeout);
+    }
+  }
+
+  // We don't have Windows 8's functions, use the emulated wait and wake funcs.
+  _patomic_wake_one_func = &emulated_wake;
+  _patomic_wake_all_func = &emulated_wake;
+  patomic_thread_fence(std::memory_order_release);
+  _patomic_wait_func = &emulated_wait;
+
+  return emulated_wait(addr, cmp, size, timeout);
+}
+
+#elif !defined(CPPPARSER) && !defined(__linux__) && defined(HAVE_POSIX_THREADS)
+
+// Same as above, but using pthreads.
+struct alignas(64) WaitTableEntry {
+  pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;
+  pthread_cond_t _cvar = PTHREAD_COND_INITIALIZER;
+  unsigned int _waiters = 0;
+};
+static WaitTableEntry _wait_table[64];
+static const size_t _wait_hash_mask = 63;
+
+/**
+ *
+ */
+void
+_patomic_wait(const volatile uint32_t *value, uint32_t old) {
+  WaitTableEntry &entry = _wait_table[std::hash<const volatile void *>{}(value) & _wait_hash_mask];
+  pthread_mutex_lock(&entry._lock);
+  ++entry._waiters;
+  while (__atomic_load_n(value, __ATOMIC_SEQ_CST) == old) {
+    pthread_cond_wait(&entry._cvar, &entry._lock);
+  }
+  --entry._waiters;
+  pthread_mutex_unlock(&entry._lock);
+}
+
+/**
+ *
+ */
+void
+_patomic_notify_all(volatile uint32_t *value) {
+  WaitTableEntry &entry = _wait_table[std::hash<const volatile void *>{}(value) & _wait_hash_mask];
+  pthread_mutex_lock(&entry._lock);
+  unsigned int num_waiters = entry._waiters;
+  pthread_mutex_unlock(&entry._lock);
+  if (num_waiters > 0) {
+    pthread_cond_broadcast(&entry._cvar);
+  }
+}
+
+#endif

+ 175 - 0
dtool/src/dtoolbase/patomic.h

@@ -0,0 +1,175 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file patomic.h
+ * @author rdb
+ * @date 2022-01-28
+ */
+
+#ifndef PATOMIC_H
+#define PATOMIC_H
+
+#include "dtoolbase.h"
+#include "selectThreadImpl.h"
+
+#include <atomic>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <windows.h>
+#endif
+
+#ifdef __linux__
+#include <linux/futex.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#endif
+
+#if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
+
+/**
+ * Dummy implementation of std::atomic that does not do any atomic operations,
+ * used when compiling without HAVE_THREADS or with SIMPLE_THREADS.
+ */
+template<class T>
+struct patomic {
+  using value_type = T;
+
+  constexpr patomic() noexcept = default;
+  constexpr patomic(T desired) noexcept;
+
+  ALWAYS_INLINE patomic(const patomic &) = delete;
+  ALWAYS_INLINE patomic &operator=(const patomic &) = delete;
+
+  static constexpr bool is_always_lock_free = true;
+  ALWAYS_INLINE bool is_lock_free() const noexcept;
+
+  ALWAYS_INLINE T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
+  ALWAYS_INLINE operator T() const noexcept;
+
+  ALWAYS_INLINE void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T operator=(T desired) noexcept;
+
+  ALWAYS_INLINE T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE bool compare_exchange_weak(T &expected, T desired,
+                                           std::memory_order success = std::memory_order_seq_cst,
+                                           std::memory_order failure = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE bool compare_exchange_strong(T &expected, T desired,
+                                             std::memory_order success = std::memory_order_seq_cst,
+                                             std::memory_order failure = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_and(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_or(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_xor(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE T operator ++(int) noexcept;
+  ALWAYS_INLINE T operator --(int) noexcept;
+  ALWAYS_INLINE T operator ++() noexcept;
+  ALWAYS_INLINE T operator --() noexcept;
+  ALWAYS_INLINE T operator +=(T arg) noexcept;
+  ALWAYS_INLINE T operator -=(T arg) noexcept;
+  ALWAYS_INLINE T operator &=(T arg) noexcept;
+  ALWAYS_INLINE T operator |=(T arg) noexcept;
+  ALWAYS_INLINE T operator ^=(T arg) noexcept;
+
+private:
+  T _value;
+};
+
+#define patomic_thread_fence(order) (std::atomic_signal_fence((order)))
+
+#else
+
+// We're using real threading, so use the real implementation.
+template<class T>
+using patomic = std::atomic<T>;
+
+#define patomic_thread_fence(order) (std::atomic_thread_fence((order)))
+
+#endif
+
+/**
+ * Implementation of atomic_unsigned_lock_free with C++20 semantics.
+ */
+class EXPCL_DTOOL_DTOOLBASE patomic_unsigned_lock_free : public patomic<uint32_t> {
+public:
+  typedef uint32_t value_type;
+
+  constexpr patomic_unsigned_lock_free() noexcept;
+  constexpr patomic_unsigned_lock_free(uint32_t desired) noexcept;
+
+  INLINE void wait(uint32_t old, std::memory_order order = std::memory_order_seq_cst) const noexcept;
+  ALWAYS_INLINE void notify_one() noexcept;
+  ALWAYS_INLINE void notify_all() noexcept;
+};
+
+/**
+ * Implementation of atomic_signed_lock_free with C++20 semantics.
+ */
+class EXPCL_DTOOL_DTOOLBASE patomic_signed_lock_free : public patomic<int32_t> {
+public:
+  typedef int32_t value_type;
+
+  constexpr patomic_signed_lock_free() noexcept;
+  constexpr patomic_signed_lock_free(int32_t desired) noexcept;
+
+  INLINE void wait(int32_t old, std::memory_order order = std::memory_order_seq_cst) const noexcept;
+  ALWAYS_INLINE void notify_one() noexcept;
+  ALWAYS_INLINE void notify_all() noexcept;
+};
+
+/**
+ * Implementation of atomic_flag with C++20 semantics.
+ */
+class EXPCL_DTOOL_DTOOLBASE patomic_flag {
+public:
+  constexpr patomic_flag() noexcept = default;
+  constexpr patomic_flag(bool desired) noexcept;
+
+  patomic_flag(const patomic_flag &) = delete;
+  patomic_flag &operator=(const patomic_flag &) = delete;
+
+  ALWAYS_INLINE void clear(std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE bool test_and_set(std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE bool test(std::memory_order order = std::memory_order_seq_cst) const noexcept;
+
+  ALWAYS_INLINE void wait(bool old, std::memory_order order = std::memory_order_seq_cst) const noexcept;
+  ALWAYS_INLINE void notify_one() noexcept;
+  ALWAYS_INLINE void notify_all() noexcept;
+
+private:
+  patomic_unsigned_lock_free _value { 0u };
+};
+
+#ifndef CPPPARSER
+ALWAYS_INLINE void patomic_wait(const volatile int32_t *value, int32_t old);
+ALWAYS_INLINE void patomic_notify_one(volatile int32_t *value);
+ALWAYS_INLINE void patomic_notify_all(volatile int32_t *value);
+
+ALWAYS_INLINE void patomic_wait(const volatile uint32_t *value, uint32_t old);
+ALWAYS_INLINE void patomic_notify_one(volatile uint32_t *value);
+ALWAYS_INLINE void patomic_notify_all(volatile uint32_t *value);
+
+#ifdef _WIN32
+EXPCL_DTOOL_DTOOLBASE extern BOOL (__stdcall *_patomic_wait_func)(volatile VOID *, PVOID, SIZE_T, DWORD);
+EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_one_func)(PVOID);
+EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_all_func)(PVOID);
+#elif !defined(__linux__) && defined(HAVE_POSIX_THREADS)
+EXPCL_DTOOL_DTOOLBASE void _patomic_wait(const volatile uint32_t *value, uint32_t old);
+EXPCL_DTOOL_DTOOLBASE void _patomic_notify_all(volatile uint32_t *value);
+#endif
+
+#include "patomic.I"
+#endif  // CPPPARSER
+
+#endif

+ 14 - 16
dtool/src/dtoolbase/typeHandle.cxx

@@ -13,7 +13,6 @@
 
 #include "typeHandle.h"
 #include "typeRegistryNode.h"
-#include "atomicAdjust.h"
 
 /**
  * Returns the total allocated memory used by objects of this type, for the
@@ -29,7 +28,7 @@ get_memory_usage(MemoryClass memory_class) const {
   } else {
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    return (size_t)AtomicAdjust::get(rnode->_memory_usage[memory_class]);
+    return rnode->_memory_usage[memory_class].load(std::memory_order_relaxed);
   }
 #endif  // DO_MEMORY_USAGE
   return 0;
@@ -48,10 +47,8 @@ inc_memory_usage(MemoryClass memory_class, size_t size) {
   if ((*this) != TypeHandle::none()) {
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    AtomicAdjust::add(rnode->_memory_usage[memory_class], (AtomicAdjust::Integer)size);
-    // cerr << *this << ".inc(" << memory_class << ", " << size << ") -> " <<
-    // rnode->_memory_usage[memory_class] << "\n";
-    if (rnode->_memory_usage[memory_class] < 0) {
+    size_t prev = rnode->_memory_usage[memory_class].fetch_add(size, std::memory_order_relaxed);
+    if (prev + size < prev) {
       std::cerr << "Memory usage overflow for type " << rnode->_name << ".\n";
       abort();
     }
@@ -72,10 +69,8 @@ dec_memory_usage(MemoryClass memory_class, size_t size) {
   if ((*this) != TypeHandle::none()) {
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    AtomicAdjust::add(rnode->_memory_usage[memory_class], -(AtomicAdjust::Integer)size);
-    // cerr << *this << ".dec(" << memory_class << ", " << size << ") -> " <<
-    // rnode->_memory_usage[memory_class] << "\n";
-    assert(rnode->_memory_usage[memory_class] >= 0);
+    size_t prev = rnode->_memory_usage[memory_class].fetch_sub(size, std::memory_order_relaxed);
+    assert(prev - size <= prev);
   }
 #endif  // DO_MEMORY_USAGE
 }
@@ -97,8 +92,8 @@ allocate_array(size_t size) {
 #endif
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    AtomicAdjust::add(rnode->_memory_usage[MC_array], (AtomicAdjust::Integer)alloc_size);
-    if (rnode->_memory_usage[MC_array] < 0) {
+    size_t prev = rnode->_memory_usage[MC_array].fetch_add(alloc_size, std::memory_order_relaxed);
+    if (prev + size < prev) {
       std::cerr << "Memory usage overflow for type " << rnode->_name << ".\n";
       abort();
     }
@@ -124,8 +119,11 @@ reallocate_array(void *old_ptr, size_t size) {
 
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    AtomicAdjust::add(rnode->_memory_usage[MC_array], (AtomicAdjust::Integer)new_size - (AtomicAdjust::Integer)old_size);
-    assert(rnode->_memory_usage[MC_array] >= 0);
+    if (new_size > old_size) {
+      rnode->_memory_usage[MC_array].fetch_add(new_size - old_size, std::memory_order_relaxed);
+    } else {
+      rnode->_memory_usage[MC_array].fetch_sub(old_size - new_size, std::memory_order_relaxed);
+    }
   }
 #else
   void *new_ptr = PANDA_REALLOC_ARRAY(old_ptr, size);
@@ -146,8 +144,8 @@ deallocate_array(void *ptr) {
   if ((*this) != TypeHandle::none()) {
     TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
     assert(rnode != nullptr);
-    AtomicAdjust::add(rnode->_memory_usage[MC_array], -(AtomicAdjust::Integer)alloc_size);
-    assert(rnode->_memory_usage[MC_array] >= 0);
+    size_t prev = rnode->_memory_usage[MC_array].fetch_sub(alloc_size, std::memory_order_relaxed);
+    assert(prev - alloc_size <= prev);
   }
 #endif  // DO_MEMORY_USAGE
   PANDA_FREE_ARRAY(ptr);

+ 0 - 1
dtool/src/dtoolbase/typeHandle_ext.cxx

@@ -38,7 +38,6 @@ make(PyTypeObject *tp) {
 PyObject *Extension<TypeHandle>::
 __reduce__() const {
   extern struct Dtool_PyTypedObject Dtool_TypeHandle;
-  extern struct Dtool_PyTypedObject Dtool_TypeRegistry;
 
   if (!*_this) {
     PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_TypeHandle, "none");

+ 1 - 2
dtool/src/dtoolbase/typeRegistryNode.cxx

@@ -23,10 +23,9 @@ bool TypeRegistryNode::_paranoid_inheritance = false;
  */
 TypeRegistryNode::
 TypeRegistryNode(TypeHandle handle, const std::string &name, TypeHandle &ref) :
-  _handle(handle), _name(name), _ref(ref)
+  _handle(handle), _name(name), _ref(ref), _memory_usage{}
 {
   clear_subtree();
-  memset(_memory_usage, 0, sizeof(_memory_usage));
 }
 
 /**

+ 2 - 1
dtool/src/dtoolbase/typeRegistryNode.h

@@ -18,6 +18,7 @@
 
 #include "typeHandle.h"
 #include "numeric_types.h"
+#include "patomic.h"
 
 #include <assert.h>
 #include <vector>
@@ -50,7 +51,7 @@ public:
   Classes _child_classes;
   PyObject *_python_type = nullptr;
 
-  AtomicAdjust::Integer _memory_usage[TypeHandle::MC_limit];
+  patomic<size_t> _memory_usage[TypeHandle::MC_limit];
 
   static bool _paranoid_inheritance;
 

+ 39 - 6
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -259,10 +259,14 @@ ns_has_environment_variable(const string &var) const {
     return true;
   }
 
-#ifndef PREREAD_ENVIRONMENT
-  return getenv(var.c_str()) != nullptr;
-#else
+#ifdef PREREAD_ENVIRONMENT
   return false;
+#elif defined(_MSC_VER)
+  size_t size = 0;
+  getenv_s(&size, nullptr, 0, var.c_str());
+  return size != 0;
+#else
+  return getenv(var.c_str()) != nullptr;
 #endif
 }
 
@@ -301,11 +305,24 @@ ns_get_environment_variable(const string &var) const {
   }
 
 #ifndef PREREAD_ENVIRONMENT
+#ifdef _MSC_VER
+  std::string value(128, '\0');
+  size_t size = value.size();
+  while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {
+    value.resize(size);
+  }
+  if (size != 0) {
+    // Strip off the trailing null byte.
+    value.resize(size - 1);
+    return value;
+  }
+#else
   const char *def = getenv(var.c_str());
   if (def != nullptr) {
     return def;
   }
 #endif
+#endif
 
 #ifdef _WIN32
   // On Windows only, we also simulate several standard folder names as
@@ -414,14 +431,15 @@ ns_get_environment_variable(const string &var) const {
 void ExecutionEnvironment::
 ns_set_environment_variable(const string &var, const string &value) {
   _variables[var] = value;
+
+#ifdef _MSC_VER
+  _putenv_s(var.c_str(), value.c_str());
+#else
   string putstr = var + "=" + value;
 
   // putenv() requires us to malloc a new C-style string.
   char *put = (char *)malloc(putstr.length() + 1);
   strcpy(put, putstr.c_str());
-#ifdef _MSC_VER
-  _putenv(put);
-#else
   putenv(put);
 #endif
 }
@@ -447,12 +465,27 @@ ns_clear_shadow(const string &var) {
 
 #ifdef PREREAD_ENVIRONMENT
   // Now we have to replace the value in the table.
+#ifdef _MSC_VER
+  std::string value(128, '\0');
+  size_t size = value.size();
+  while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {
+    value.resize(size);
+  }
+  if (size != 0) {
+    // Strip off the trailing null byte.
+    value.resize(size - 1);
+    (*vi).second = std::move(value);
+  } else {
+    _variables.erase(vi);
+  }
+#else
   const char *def = getenv(var.c_str());
   if (def != nullptr) {
     (*vi).second = def;
   } else {
     _variables.erase(vi);
   }
+#endif
 #endif  // PREREAD_ENVIRONMENT
 }
 

+ 15 - 1
dtool/src/dtoolutil/filename.cxx

@@ -152,10 +152,22 @@ get_panda_root() {
 
   if (panda_root == nullptr) {
     panda_root = new string;
+
+#ifdef _MSC_VER
+    char *envvar = nullptr;
+    size_t size = 0;
+    while (getenv_s(&size, envvar, size, "PANDA_ROOT") == ERANGE) {
+      envvar = (char *)alloca(size);
+    }
+    if (size != 0) {
+      (*panda_root) = front_to_back_slash(envvar);
+    }
+#else
     const char *envvar = getenv("PANDA_ROOT");
     if (envvar != nullptr) {
       (*panda_root) = front_to_back_slash(envvar);
     }
+#endif
 
     // Ensure the string ends in a backslash.  If PANDA_ROOT is empty or
     // undefined, this function must return a single backslash--not an empty
@@ -477,7 +489,8 @@ get_home_directory() {
   if (AtomicAdjust::get_ptr(_home_directory) == nullptr) {
     Filename home_directory;
 
-    // In all environments, check $HOME first.
+    // In all environments except Windows, check $HOME first.
+#ifndef _WIN32
     char *home = getenv("HOME");
     if (home != nullptr) {
       Filename dirname = from_os_specific(home);
@@ -487,6 +500,7 @@ get_home_directory() {
         }
       }
     }
+#endif
 
     if (home_directory.empty()) {
 #ifdef _WIN32

+ 2 - 0
dtool/src/dtoolutil/pandaSystem.cxx

@@ -53,6 +53,8 @@ PandaSystem() :
   set_system_tag("system", "malloc", "dlmalloc");
 #elif defined(USE_MEMORY_PTMALLOC2)
   set_system_tag("system", "malloc", "ptmalloc2");
+#elif defined(USE_MEMORY_MIMALLOC)
+  set_system_tag("system", "malloc", "mimalloc");
 #else
   set_system_tag("system", "malloc", "malloc");
 #endif

+ 10 - 2
dtool/src/dtoolutil/panda_getopt_impl.cxx

@@ -140,6 +140,10 @@ PandaGetopt(int argc, char *const argv[], const char *optstring,
   // _options[0] is used for invalid characters.
   _options.push_back(Option('?', no_argument));
 
+#ifdef _MSC_VER
+  size_t size;
+#endif
+
   if (optstring[0] == '-') {
     // RETURN_IN_ORDER: Non-option arguments (operands) are handled as if they
     // were the argument to an option with the value 1 ('\001').
@@ -154,8 +158,12 @@ PandaGetopt(int argc, char *const argv[], const char *optstring,
     // argument is reached, or when the element of argv is "--".
     ++optstring;
     _require_order = true;
-
-  } else if (getenv("POSIXLY_CORRECT") != nullptr) {
+  }
+#ifdef _MSC_VER
+  else if (getenv_s(&size, nullptr, 0, "POSIXLY_CORRECT") == 0 && size != 0) {
+#else
+  else if (getenv("POSIXLY_CORRECT") != nullptr) {
+#endif
     // REQUIRE_ORDER.
     _require_order = true;
 

+ 3 - 4
dtool/src/dtoolutil/pfstreamBuf.cxx

@@ -331,8 +331,9 @@ open_pipe(const string &cmd) {
 
   // Both WinExec() and CreateProcess() want a non-const char pointer.  Maybe
   // they change it, and maybe they don't.  I'm not taking chances.
-  char *cmdline = new char[cmd.length() + 1];
-  strcpy(cmdline, cmd.c_str());
+  char *cmdline = (char *)alloca(cmd.size() + 1);
+  memcpy(cmdline, cmd.data(), cmd.size());
+  cmdline[cmd.size()] = 0;
 
   // We should be using CreateProcess() instead of WinExec(), but that seems
   // to be likely to crash Win98.  WinExec() seems better behaved, and it's
@@ -345,8 +346,6 @@ open_pipe(const string &cmd) {
     // Don't return yet, since we still need to clean up.
   }
 
-  delete[] cmdline;
-
   // Now restore our own stdout, up here in the parent process.
   if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) {
 #ifndef NDEBUG

+ 3 - 0
dtool/src/interrogate/functionRemap.cxx

@@ -327,6 +327,9 @@ make_wrapper_entry(FunctionIndex function_index) {
     if ((*pi)._has_name) {
       param._parameter_flags |= InterrogateFunctionWrapper::PF_has_name;
     }
+    if ((*pi)._remap->has_default_value()) {
+      param._parameter_flags |= InterrogateFunctionWrapper::PF_is_optional;
+    }
     iwrapper._parameters.push_back(param);
   }
 

+ 4 - 4
dtool/src/interrogate/interfaceMaker.cxx

@@ -77,8 +77,8 @@ InterfaceMaker::Function::
  */
 InterfaceMaker::MakeSeq::
 MakeSeq(const string &name, const InterrogateMakeSeq &imake_seq) :
-  _name(name),
   _imake_seq(imake_seq),
+  _name(name),
   _length_getter(nullptr),
   _element_getter(nullptr)
 {
@@ -237,7 +237,7 @@ generate_wrappers() {
 
   int num_global_elements = idb->get_num_global_elements();
   for (int gi = 0; gi < num_global_elements; ++gi) {
-    printf(" Global Type = %d", gi);
+    //printf(" Global Type = %d", gi);
     TypeIndex type_index = idb->get_global_element(gi);
     record_object(type_index);
   }
@@ -256,12 +256,12 @@ generate_wrappers() {
       FunctionIndex func_index = iman.get_getter();
       record_function(dummy_type, func_index);
     }
-    printf(" Manifests %d\n", mi);
+    //printf(" Manifests %d\n", mi);
   }
 
   int num_elements = idb->get_num_global_elements();
   for (int ei = 0; ei < num_elements; ei++) {
-    printf(" Element %d\n", ei);
+    //printf(" Element %d\n", ei);
 
     ElementIndex element_index = idb->get_global_element(ei);
     const InterrogateElement &ielement = idb->get_element(element_index);

+ 2 - 2
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -2510,11 +2510,11 @@ write_module_class(ostream &out, Object *obj) {
           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, RF_pyobject | RF_err_null | RF_decref_args, true);
+                                true, true, AT_varargs, return_flags | RF_decref_args, true);
           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, RF_pyobject | RF_err_null, true);
+                                true, true, AT_single_arg, return_flags, true);
           out << "  }\n\n";
 
           out << "  if (!_PyErr_OCCURRED()) {\n";

+ 2 - 2
dtool/src/interrogate/interfaceMakerPythonObj.cxx

@@ -102,10 +102,10 @@ write_module(ostream &out,ostream *out_h, InterrogateModuleDef *def) {
          ++fi) {
       Function *func = (*fi);
       out << "  { \"" << func->_ifunc.get_name() << "\", &" << func->_name
-          << ", METH_VARARGS },\n";
+          << ", METH_VARARGS, nullptr },\n";
     }
   }
-  out << "  { nullptr, nullptr }\n"
+  out << "  { nullptr, nullptr, 0, nullptr }\n"
       << "};\n\n"
 
       << "#if PY_MAJOR_VERSION >= 3\n"

+ 2 - 2
dtool/src/interrogate/interfaceMakerPythonSimple.cxx

@@ -89,10 +89,10 @@ write_module(ostream &out,ostream *out_h, InterrogateModuleDef *def) {
     for (ri = func->_remaps.begin(); ri != func->_remaps.end(); ++ri) {
       FunctionRemap *remap = (*ri);
       out << "  { \"" << remap->_reported_name << "\", &"
-          << remap->_wrapper_name << ", METH_VARARGS },\n";
+          << remap->_wrapper_name << ", METH_VARARGS, nullptr },\n";
     }
   }
-  out << "  { nullptr, nullptr }\n"
+  out << "  { nullptr, nullptr, 0, nullptr }\n"
       << "};\n\n"
 
       << "#if PY_MAJOR_VERSION >= 3\n"

+ 7 - 0
dtool/src/interrogate/interrogate.cxx

@@ -547,8 +547,15 @@ main(int argc, char **argv) {
   // We allow overriding this value by setting SOURCE_DATE_EPOCH to support
   // reproducible builds.
   int file_identifier;
+#ifdef _MSC_VER
+  char source_date_epoch[64];
+  size_t source_date_epoch_size = 0;
+  if (getenv_s(&source_date_epoch_size, source_date_epoch,
+               sizeof(source_date_epoch), "SOURCE_DATE_EPOCH"), source_date_epoch_size > 1) {
+#else
   const char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
   if (source_date_epoch != nullptr && source_date_epoch[0] != 0) {
+#endif
     file_identifier = atoi(source_date_epoch);
   } else {
     file_identifier = time(nullptr);

+ 22 - 3
dtool/src/interrogate/interrogateBuilder.cxx

@@ -1071,6 +1071,10 @@ scan_function(CPPInstance *function) {
     return;
   }
 
+  if (TypeManager::involves_rvalue_reference(ftype)) {
+    return;
+  }
+
   get_function(function, "",
                nullptr, scope,
                InterrogateFunction::F_global);
@@ -1770,6 +1774,16 @@ get_function(CPPInstance *function, string description,
     ifunction->_flags |= InterrogateFunction::F_operator_typecast;
   }
 
+  if (ftype->_flags & CPPFunctionType::F_constructor) {
+    // This is a constructor.
+    ifunction->_flags |= InterrogateFunction::F_constructor;
+  }
+
+  if (ftype->_flags & CPPFunctionType::F_destructor) {
+    // This is a destructor.
+    ifunction->_flags |= InterrogateFunction::F_destructor;
+  }
+
   if (function->_storage_class & CPPInstance::SC_virtual) {
     // This is a virtual function.
     ifunction->_flags |= InterrogateFunction::F_virtual;
@@ -2747,7 +2761,8 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
     function->_storage_class |= CPPInstance::SC_inline | CPPInstance::SC_defaulted;
     function->_vis = V_published;
 
-    FunctionIndex index = get_function(function, "", cpptype, cpptype->get_scope(), 0);
+    FunctionIndex index = get_function(function, "", cpptype, cpptype->get_scope(),
+                                       InterrogateFunction::F_constructor);
     if (find(itype._constructors.begin(), itype._constructors.end(),
              index) == itype._constructors.end()) {
       itype._constructors.push_back(index);
@@ -2773,7 +2788,8 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
     function->_storage_class |= CPPInstance::SC_inline | CPPInstance::SC_defaulted;
     function->_vis = V_published;
 
-    FunctionIndex index = get_function(function, "", cpptype, cpptype->get_scope(), 0);
+    FunctionIndex index = get_function(function, "", cpptype, cpptype->get_scope(),
+                                       InterrogateFunction::F_constructor);
     if (find(itype._constructors.begin(), itype._constructors.end(),
              index) == itype._constructors.end()) {
       itype._constructors.push_back(index);
@@ -2814,7 +2830,7 @@ define_struct_type(InterrogateType &itype, CPPStructType *cpptype,
 
     itype._destructor = get_function(function, "",
                                      cpptype, cpptype->get_scope(),
-                                     0);
+                                     InterrogateFunction::F_destructor);
     itype._flags |= InterrogateType::F_implicit_destructor;
   }
 }
@@ -2988,6 +3004,9 @@ define_method(CPPInstance *function, InterrogateType &itype,
     // If it isn't, we should publish this method anyway.
   }
 
+  if (TypeManager::involves_rvalue_reference(ftype)) {
+    return;
+  }
 
   FunctionIndex index = get_function(function, "", struct_type, scope, 0);
   if (index != 0) {

+ 40 - 0
dtool/src/interrogate/typeManager.cxx

@@ -2017,6 +2017,46 @@ involves_protected(CPPType *type) {
   }
 }
 
+/**
+ * Returns true if the type involves an rvalue reference.
+ */
+bool TypeManager::
+involves_rvalue_reference(CPPType *type) {
+  switch (type->get_subtype()) {
+  case CPPDeclaration::ST_const:
+    return involves_rvalue_reference(type->as_const_type()->_wrapped_around);
+
+  case CPPDeclaration::ST_reference:
+    return type->as_reference_type()->_value_category == CPPReferenceType::VC_rvalue;
+
+  case CPPDeclaration::ST_pointer:
+    return involves_rvalue_reference(type->as_pointer_type()->_pointing_at);
+
+  case CPPDeclaration::ST_function:
+    {
+      CPPFunctionType *ftype = type->as_function_type();
+      if (involves_rvalue_reference(ftype->_return_type)) {
+        return true;
+      }
+      const CPPParameterList::Parameters &params =
+        ftype->_parameters->_parameters;
+      CPPParameterList::Parameters::const_iterator pi;
+      for (pi = params.begin(); pi != params.end(); ++pi) {
+        if (involves_rvalue_reference((*pi)->_type)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+  case CPPDeclaration::ST_typedef:
+    return involves_rvalue_reference(type->as_typedef_type()->_type);
+
+  default:
+    return false;
+  }
+}
+
 /**
  * Returns the type this pointer type points to.
  */

+ 1 - 0
dtool/src/interrogate/typeManager.h

@@ -118,6 +118,7 @@ public:
   static bool is_handle(CPPType *type);
   static bool involves_unpublished(CPPType *type);
   static bool involves_protected(CPPType *type);
+  static bool involves_rvalue_reference(CPPType *type);
 
   static bool is_ostream(CPPType *type);
   static bool is_pointer_to_ostream(CPPType *type);

+ 11 - 0
dtool/src/interrogatedb/interrogateDatabase.cxx

@@ -968,6 +968,17 @@ read_new(std::istream &in, InterrogateModuleDef *def) {
 
       add_type(index, type);
       num_types--;
+
+      // Older versions of interrogate were not setting these flags.
+      const InterrogateType &itype = get_type(index);
+      FunctionIndex dtor = itype.get_destructor();
+      if (dtor != 0) {
+        update_function(dtor)._flags |= InterrogateFunction::F_destructor;
+      }
+      for (int i = 0; i < itype.number_of_constructors(); ++i) {
+        FunctionIndex ctor = itype.get_constructor(i);
+        update_function(ctor)._flags |= InterrogateFunction::F_constructor;
+      }
     }
   }
 

+ 16 - 0
dtool/src/interrogatedb/interrogateFunction.I

@@ -54,6 +54,22 @@ is_operator_typecast() const {
   return (_flags & F_operator_typecast) != 0;
 }
 
+/**
+ * Returns true if the function is a constructor.
+ */
+INLINE bool InterrogateFunction::
+is_constructor() const {
+  return (_flags & F_constructor) != 0;
+}
+
+/**
+ * Returns true if the function is a destructor.
+ */
+INLINE bool InterrogateFunction::
+is_destructor() const {
+  return (_flags & F_destructor) != 0;
+}
+
 /**
  * Return the class that owns the method, if is_method() returns true.
  */

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

@@ -38,6 +38,8 @@ public:
   INLINE bool is_method() const;
   INLINE bool is_unary_op() const;
   INLINE bool is_operator_typecast() const;
+  INLINE bool is_constructor() const;
+  INLINE bool is_destructor() const;
   INLINE TypeIndex get_class() const;
 
   INLINE bool has_scoped_name() const;
@@ -70,7 +72,9 @@ private:
     F_setter          = 0x0020,
     F_unary_op        = 0x0040,
     F_operator_typecast = 0x0080,
-    F_item_assignment = 0x0100,
+    F_constructor     = 0x0100,
+    F_destructor      = 0x0200,
+    F_item_assignment = 0x0400,
   };
 
   int _flags;
@@ -98,6 +102,7 @@ public:
   std::string _expression;
 
   friend class InterrogateBuilder;
+  friend class InterrogateDatabase;
   friend class InterfaceMakerC;
   friend class InterfaceMakerPythonSimple;
   friend class InterfaceMakerPythonNative;

+ 11 - 0
dtool/src/interrogatedb/interrogateFunctionWrapper.I

@@ -148,6 +148,17 @@ parameter_is_this(int n) const {
   return false;
 }
 
+/**
+ *
+ */
+INLINE bool InterrogateFunctionWrapper::
+parameter_is_optional(int n) const {
+  if (n >= 0 && n < (int)_parameters.size()) {
+    return (_parameters[n]._parameter_flags & PF_is_optional) != 0;
+  }
+  return false;
+}
+
 /**
  *
  */

+ 2 - 0
dtool/src/interrogatedb/interrogateFunctionWrapper.h

@@ -45,6 +45,7 @@ public:
   INLINE bool parameter_has_name(int n) const;
   INLINE const std::string &parameter_get_name(int n) const;
   INLINE bool parameter_is_this(int n) const;
+  INLINE bool parameter_is_optional(int n) const;
 
   INLINE const std::string &get_unique_name() const;
 
@@ -66,6 +67,7 @@ private:
   enum ParameterFlags {
     PF_has_name       = 0x0001,
     PF_is_this        = 0x0002,
+    PF_is_optional    = 0x0004,
   };
 
   int _flags;

+ 67 - 1
dtool/src/interrogatedb/interrogate_interface.cxx

@@ -266,6 +266,31 @@ interrogate_function_class(FunctionIndex function) {
   return InterrogateDatabase::get_ptr()->get_function(function).get_class();
 }
 
+bool
+interrogate_function_is_unary_op(FunctionIndex function) {
+  // cerr << "interrogate_function_is_unary_op(" << function << ")\n";
+  return InterrogateDatabase::get_ptr()->get_function(function).is_unary_op();
+}
+
+bool
+interrogate_function_is_operator_typecast(FunctionIndex function) {
+  // cerr << "interrogate_function_is_operator_typecast(" << function <<
+  // ")\n";
+  return InterrogateDatabase::get_ptr()->get_function(function).is_operator_typecast();
+}
+
+bool
+interrogate_function_is_constructor(FunctionIndex function) {
+  // cerr << "interrogate_function_is_constructor(" << function << ")\n";
+  return InterrogateDatabase::get_ptr()->get_function(function).is_constructor();
+}
+
+bool
+interrogate_function_is_destructor(FunctionIndex function) {
+  // cerr << "interrogate_function_is_destructor(" << function << ")\n";
+  return InterrogateDatabase::get_ptr()->get_function(function).is_destructor();
+}
+
 bool
 interrogate_function_has_module_name(FunctionIndex function) {
   // cerr << "interrogate_function_has_module_name(" << function << ")\n";
@@ -411,6 +436,12 @@ interrogate_wrapper_parameter_is_this(FunctionWrapperIndex wrapper, int n) {
   return InterrogateDatabase::get_ptr()->get_wrapper(wrapper).parameter_is_this(n);
 }
 
+bool
+interrogate_wrapper_parameter_is_optional(FunctionWrapperIndex wrapper, int n) {
+  // cerr << "interrogate_wrapper_is_optional(" << wrapper << ", " << n << ")\n";
+  return InterrogateDatabase::get_ptr()->get_wrapper(wrapper).parameter_is_optional(n);
+}
+
 bool
 interrogate_wrapper_has_pointer(FunctionWrapperIndex wrapper) {
   // cerr << "interrogate_wrapper_has_pointer(" << wrapper << ")\n";
@@ -476,11 +507,22 @@ interrogate_make_seq_num_name(MakeSeqIndex make_seq) {
 const char *
 interrogate_make_seq_element_name(MakeSeqIndex make_seq) {
   // cerr << "interrogate_make_seq_element_name(" << make_seq << ")\n";
-  static string result;
   FunctionIndex function = InterrogateDatabase::get_ptr()->get_make_seq(make_seq).get_element_getter();
   return interrogate_function_name(function);
 }
 
+FunctionIndex
+interrogate_make_seq_num_getter(MakeSeqIndex make_seq) {
+  // cerr << "interrogate_make_seq_num_getter(" << make_seq << ")\n";
+  return InterrogateDatabase::get_ptr()->get_make_seq(make_seq).get_length_getter();
+}
+
+FunctionIndex
+interrogate_make_seq_element_getter(MakeSeqIndex make_seq) {
+  // cerr << "interrogate_make_seq_element_getter(" << make_seq << ")\n";
+  return InterrogateDatabase::get_ptr()->get_make_seq(make_seq).get_element_getter();
+}
+
 int
 interrogate_number_of_global_types() {
   // cerr << "interrogate_number_of_global_types()\n";
@@ -668,12 +710,30 @@ interrogate_type_wrapped_type(TypeIndex type) {
   return InterrogateDatabase::get_ptr()->get_type(type).get_wrapped_type();
 }
 
+bool
+interrogate_type_is_array(TypeIndex type) {
+  // cerr << "interrogate_type_is_array(" << type << ")\n";
+  return InterrogateDatabase::get_ptr()->get_type(type).is_array();
+}
+
+int
+interrogate_type_array_size(TypeIndex type) {
+  // cerr << "interrogate_type_array_size(" << type << ")\n";
+  return InterrogateDatabase::get_ptr()->get_type(type).get_array_size();
+}
+
 bool
 interrogate_type_is_enum(TypeIndex type) {
   // cerr << "interrogate_type_is_enum(" << type << ")\n";
   return InterrogateDatabase::get_ptr()->get_type(type).is_enum();
 }
 
+bool
+interrogate_type_is_scoped_enum(TypeIndex type) {
+  // cerr << "interrogate_type_is_scoped_enum(" << type << ")\n";
+  return InterrogateDatabase::get_ptr()->get_type(type).is_scoped_enum();
+}
+
 int
 interrogate_type_number_of_enum_values(TypeIndex type) {
   // cerr << "interrogate_type_number_of_enum_values(" << type << ")\n";
@@ -828,6 +888,12 @@ interrogate_type_get_derivation(TypeIndex type, int n) {
   return InterrogateDatabase::get_ptr()->get_type(type).get_derivation(n);
 }
 
+bool
+interrogate_type_is_final(TypeIndex type) {
+  // cerr << "interrogate_type_is_final(" << type << ")\n";
+  return InterrogateDatabase::get_ptr()->get_type(type).is_final();
+}
+
 bool
 interrogate_type_derivation_has_upcast(TypeIndex type, int n) {
   // cerr << "interrogate_type_derivation_has_upcast(" << type << ", " << n <<

+ 13 - 1
dtool/src/interrogatedb/interrogate_interface.h

@@ -210,6 +210,10 @@ EXPCL_INTERROGATEDB const char *interrogate_function_prototype(FunctionIndex fun
 // if the function is a class method.
 EXPCL_INTERROGATEDB bool interrogate_function_is_method(FunctionIndex function);
 EXPCL_INTERROGATEDB TypeIndex interrogate_function_class(FunctionIndex function);
+EXPCL_INTERROGATEDB bool interrogate_function_is_unary_op(FunctionIndex function);
+EXPCL_INTERROGATEDB bool interrogate_function_is_operator_typecast(FunctionIndex function);
+EXPCL_INTERROGATEDB bool interrogate_function_is_constructor(FunctionIndex function);
+EXPCL_INTERROGATEDB bool interrogate_function_is_destructor(FunctionIndex function);
 
 // This returns the module name reported for the function, if available.
 EXPCL_INTERROGATEDB bool interrogate_function_has_module_name(FunctionIndex function);
@@ -299,6 +303,7 @@ EXPCL_INTERROGATEDB TypeIndex interrogate_wrapper_parameter_type(FunctionWrapper
 EXPCL_INTERROGATEDB bool interrogate_wrapper_parameter_has_name(FunctionWrapperIndex wrapper, int n);
 EXPCL_INTERROGATEDB const char *interrogate_wrapper_parameter_name(FunctionWrapperIndex wrapper, int n);
 EXPCL_INTERROGATEDB bool interrogate_wrapper_parameter_is_this(FunctionWrapperIndex wrapper, int n);
+EXPCL_INTERROGATEDB bool interrogate_wrapper_parameter_is_optional(FunctionWrapperIndex wrapper, int n);
 
 // This returns a pointer to a function that may be called to invoke the
 // function, if the -fptrs option to return function pointers was specified to
@@ -345,7 +350,8 @@ EXPCL_INTERROGATEDB const char *interrogate_make_seq_comment(ElementIndex elemen
 EXPCL_INTERROGATEDB const char *interrogate_make_seq_num_name(MakeSeqIndex make_seq);
 // The name of the real method that returns the nth element, e.g.  "get_thing"
 EXPCL_INTERROGATEDB const char *interrogate_make_seq_element_name(MakeSeqIndex make_seq);
-
+EXPCL_INTERROGATEDB FunctionIndex interrogate_make_seq_num_getter(MakeSeqIndex make_seq);
+EXPCL_INTERROGATEDB FunctionIndex interrogate_make_seq_element_getter(MakeSeqIndex make_seq);
 
 // Types
 
@@ -420,9 +426,14 @@ EXPCL_INTERROGATEDB bool interrogate_type_is_const(TypeIndex type);
 EXPCL_INTERROGATEDB bool interrogate_type_is_typedef(TypeIndex type);
 EXPCL_INTERROGATEDB TypeIndex interrogate_type_wrapped_type(TypeIndex type);
 
+// If interrogate_type_is_array() returns true, this is an array type.
+EXPCL_INTERROGATEDB bool interrogate_type_is_array(TypeIndex type);
+EXPCL_INTERROGATEDB int interrogate_type_array_size(TypeIndex type);
+
 // If interrogate_type_is_enum() returns true, this is an enumerated type,
 // which means it may take any one of a number of named integer values.
 EXPCL_INTERROGATEDB bool interrogate_type_is_enum(TypeIndex type);
+EXPCL_INTERROGATEDB bool interrogate_type_is_scoped_enum(TypeIndex type);
 EXPCL_INTERROGATEDB int interrogate_type_number_of_enum_values(TypeIndex type);
 EXPCL_INTERROGATEDB const char *interrogate_type_enum_value_name(TypeIndex type, int n);
 EXPCL_INTERROGATEDB const char *interrogate_type_enum_value_scoped_name(TypeIndex type, int n);
@@ -499,6 +510,7 @@ EXPCL_INTERROGATEDB FunctionIndex interrogate_type_get_cast(TypeIndex type, int
 // list of base classes for this particular type.
 EXPCL_INTERROGATEDB int interrogate_type_number_of_derivations(TypeIndex type);
 EXPCL_INTERROGATEDB TypeIndex interrogate_type_get_derivation(TypeIndex type, int n);
+EXPCL_INTERROGATEDB bool interrogate_type_is_final(TypeIndex type);
 
 // For each base class, we might need to define an explicit upcast or downcast
 // operation to convert the pointer to the derived class to an appropriate

+ 2 - 2
dtool/src/interrogatedb/py_panda.cxx

@@ -744,7 +744,7 @@ PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) {
   if (callable == nullptr) {
     return nullptr;
   }
-  PyObject *result = _PyObject_CallNoArg(callable);
+  PyObject *result = PyObject_CallNoArgs(callable);
   Py_DECREF(callable);
   return result;
 }
@@ -768,7 +768,7 @@ PyObject *map_deepcopy_to_copy(PyObject *self, PyObject *args) {
   if (callable == nullptr) {
     return nullptr;
   }
-  PyObject *result = _PyObject_CallNoArg(callable);
+  PyObject *result = PyObject_CallNoArgs(callable);
   Py_DECREF(callable);
   return result;
 }

+ 6 - 0
dtool/src/prc/CMakeLists.txt

@@ -94,6 +94,12 @@ if(ANDROID)
   target_link_libraries(p3prc log)
 endif()
 
+if(HAVE_OPENSSL AND CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
+  # Do not re-export symbols from these libraries.
+  target_link_options(p3prc PRIVATE "LINKER:--exclude-libs,libssl.a")
+  target_link_options(p3prc PRIVATE "LINKER:--exclude-libs,libcrypto.a")
+endif()
+
 install(TARGETS p3prc
   EXPORT Core COMPONENT Core
   DESTINATION ${CMAKE_INSTALL_LIBDIR}

+ 1 - 0
dtool/src/prc/configFlags.h

@@ -17,6 +17,7 @@
 #include "dtoolbase.h"
 #include "numeric_types.h"
 #include "atomicAdjust.h"
+#include "memoryBase.h"
 
 /**
  * This class is the base class of both ConfigVariable and ConfigVariableCore.

+ 2 - 1
dtool/src/prc/configVariableBase.h

@@ -21,6 +21,7 @@
 #include "configVariableManager.h"
 #include "vector_string.h"
 #include "pset.h"
+#include "memoryBase.h"
 
 // Use this macro to wrap around a description passed to a ConfigVariable
 // constructor.  This allows the description to be completely compiled out, so
@@ -42,7 +43,7 @@
  * and/or ConfigDeclaration, more or less duplicating the interface presented
  * there.
  */
-class EXPCL_DTOOL_PRC ConfigVariableBase : public ConfigFlags {
+class EXPCL_DTOOL_PRC ConfigVariableBase : public ConfigFlags, public MemoryBase {
 protected:
   INLINE ConfigVariableBase(const std::string &name, ValueType type);
   ConfigVariableBase(const std::string &name, ValueType type,

+ 2 - 1
dtool/src/prc/configVariableCore.h

@@ -18,6 +18,7 @@
 #include "configFlags.h"
 #include "configPageManager.h"
 #include "pnotify.h"
+#include "memoryBase.h"
 
 #include <vector>
 
@@ -31,7 +32,7 @@ class ConfigDeclaration;
  * make() method, which may return a shared instance.  Once created, these
  * objects are never destructed.
  */
-class EXPCL_DTOOL_PRC ConfigVariableCore : public ConfigFlags {
+class EXPCL_DTOOL_PRC ConfigVariableCore : public ConfigFlags, public MemoryBase {
 private:
   ConfigVariableCore(const std::string &name);
   ConfigVariableCore(const ConfigVariableCore &templ, const std::string &name);

+ 2 - 5
dtool/src/prc/notify.cxx

@@ -18,13 +18,10 @@
 #include "configVariableBool.h"
 #include "filename.h"
 #include "config_prc.h"
+#include "patomic.h"
 
 #include <ctype.h>
 
-#ifdef PHAVE_ATOMIC
-#include <atomic>
-#endif
-
 #ifdef BUILD_IPHONE
 #include <fcntl.h>
 #endif
@@ -439,7 +436,7 @@ config_initialized() {
        "The filename to which to write all the output of notify");
 
     // We use this to ensure that only one thread can initialize the output.
-    static std::atomic_flag initialized = ATOMIC_FLAG_INIT;
+    static patomic_flag initialized = ATOMIC_FLAG_INIT;
 
     std::string value = notify_output.get_value();
     if (!value.empty() && !initialized.test_and_set()) {

+ 1 - 1
dtool/src/prc/notifyCategory.I

@@ -49,7 +49,7 @@ set_severity(NotifySeverity severity) {
   _severity = severity;
 #else
   // enforce the no-debug, no-spam rule.
-  _severity = std::max(severity, NS_info);
+  _severity = (std::max)(severity, NS_info);
 #endif
   invalidate_cache();
 }

+ 7 - 2
makepanda/installpanda.py

@@ -10,10 +10,12 @@
 
 import os
 import sys
-import sysconfig
 from optparse import OptionParser
 from makepandacore import *
 
+# DO NOT CHANGE TO sysconfig - see GitHub issue #1230
+from distutils.sysconfig import get_python_lib
+
 
 MIME_INFO = (
     ("egg", "model/x-egg", "EGG model file", "pview"),
@@ -135,13 +137,16 @@ def GetLibDir():
 
     if os.path.isfile('/etc/debian_version'):
         return GetDebLibDir()
+    elif os.path.isfile('/etc/arch-release'):
+        # ArchLinux has lib64, but it is a symlink to lib.
+        return "lib"
     else:
         # Okay, maybe we're on an RPM-based system?
         return GetRPMLibDir()
 
     # If Python is installed into /usr/lib64, it's probably safe
     # to assume that we should install there as well.
-    python_lib = sysconfig.get_path("platlib")
+    python_lib = get_python_lib(1)
     if python_lib.startswith('/usr/lib64/') or \
        python_lib.startswith('/usr/local/lib64/'):
         return "lib64"

+ 60 - 26
makepanda/makepackage.py

@@ -6,7 +6,6 @@ import shutil
 import glob
 import re
 import subprocess
-import sysconfig
 from makepandacore import *
 from installpanda import *
 
@@ -190,47 +189,73 @@ def MakeInstallerNSIS(version, file, title, installdir, compressor="lzma", **kwa
     oscmd(cmd)
 
 
-def MakeDebugSymbolArchive(zipname, dirname):
-    outputdir = GetOutputDir()
-
+def MakeDebugSymbolZipArchive(zipname):
     import zipfile
 
-    zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
+    outputdir = GetOutputDir()
+    zip = zipfile.ZipFile(zipname + '.zip', 'w', zipfile.ZIP_DEFLATED)
 
     for fn in glob.glob(os.path.join(outputdir, 'bin', '*.pdb')):
-        zip.write(fn, dirname + '/bin/' + os.path.basename(fn))
+        zip.write(fn, 'bin/' + os.path.basename(fn))
 
     for fn in glob.glob(os.path.join(outputdir, 'panda3d', '*.pdb')):
-        zip.write(fn, dirname + '/panda3d/' + os.path.basename(fn))
+        zip.write(fn, 'panda3d/' + os.path.basename(fn))
 
     for fn in glob.glob(os.path.join(outputdir, 'plugins', '*.pdb')):
-        zip.write(fn, dirname + '/plugins/' + os.path.basename(fn))
+        zip.write(fn, 'plugins/' + os.path.basename(fn))
 
     for fn in glob.glob(os.path.join(outputdir, 'python', '*.pdb')):
-        zip.write(fn, dirname + '/python/' + os.path.basename(fn))
+        zip.write(fn, 'python/' + os.path.basename(fn))
 
     for fn in glob.glob(os.path.join(outputdir, 'python', 'DLLs', '*.pdb')):
-        zip.write(fn, dirname + '/python/DLLs/' + os.path.basename(fn))
+        zip.write(fn, 'python/DLLs/' + os.path.basename(fn))
 
     zip.close()
 
 
+def MakeDebugSymbolSevenZipArchive(zipname, compressor):
+    zipname += '.7z'
+    flags = ['-t7z', '-y']
+
+    if compressor == 'zlib':
+        # This will still build an LZMA2 archive by default,
+        # but will complete significantly faster.
+        flags.extend(['-mx=3'])
+
+    # Remove the old archive before proceeding.
+    if os.path.exists(zipname):
+        os.remove(zipname)
+
+    outputdir = GetOutputDir()
+
+    # We'll be creating the archive inside the output
+    # directory, so we need the relative path to the archive
+    zipname = os.path.relpath(zipname, outputdir)
+
+    # Create a 7-zip archive, including all *.pdb files
+    # that are not in the tmp folder
+    cmd = [GetSevenZip(), 'a']
+    cmd.extend(flags)
+    cmd.extend(['-ir!*.pdb', '-x!' + os.path.join('tmp', '*'), zipname])
+
+    subprocess.call(cmd, stdout=subprocess.DEVNULL, cwd=outputdir)
+
+
+def MakeDebugSymbolArchive(zipname, compressor):
+    if HasSevenZip():
+        MakeDebugSymbolSevenZipArchive(zipname, compressor)
+    else:
+        MakeDebugSymbolZipArchive(zipname)
+
+
 def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
                        python_versions=[], **kwargs):
     outputdir = GetOutputDir()
 
-    # We pack the default Python 3 version that ships with Ubuntu.
-    python3_ver = None
+    # Only pack the versions of Python included with this Ubuntu version.
     install_python_versions = []
-
-    # What's the system version of Python 3?
-    oscmd('python3 -V > "%s/tmp/python3_version.txt"' % (outputdir))
-    sys_python3_ver = '.'.join(ReadFile(outputdir + "/tmp/python3_version.txt").strip().split(' ')[1].split('.')[:2])
-
-    # Check that we built with support for it.
     for version_info in python_versions:
-        if version_info["version"] == sys_python3_ver:
-            python3_ver = sys_python3_ver
+        if os.path.isdir("/usr/lib/python" + version_info["version"]):
             install_python_versions.append(version_info)
 
     major_version = '.'.join(version.split('.')[:2])
@@ -317,9 +342,13 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
         recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
         provides = "panda3d"
 
-        if python3_ver:
-            depends += ", python%s" % (python3_ver)
-            recommends += ", python-pmw, python3-tk (>= %s)" % (python3_ver)
+        # Require at least one of the Python versions we built for.
+        if install_python_versions:
+            depends += ", " + " | ".join("python" + version_info["version"] for version_info in install_python_versions)
+
+            # But recommend the system version of Python 3.
+            recommends += ", python3"
+            recommends += ", python3-tk"
             provides += ", python3-panda3d"
 
         # Write back the dependencies, and delete the dummy set-up.
@@ -333,7 +362,7 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
         oscmd("chmod -R 755 targetroot/DEBIAN")
         oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums")
         oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols")
-        oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
+        oscmd("fakeroot dpkg-deb -Zxz -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
 
     elif rpmbuild_present:
         # Invoke installpanda.py to install it into a temporary dir
@@ -910,7 +939,9 @@ def MakeInstallerAndroid(version, **kwargs):
                     shutil.copy(os.path.join(source_dir, base), target)
 
     # Copy the Python standard library to the .apk as well.
-    stdlib_source = sysconfig.get_path("stdlib")
+    # DO NOT CHANGE TO sysconfig - see #1230
+    from distutils.sysconfig import get_python_lib
+    stdlib_source = get_python_lib(False, True)
     stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
     copy_python_tree(stdlib_source, stdlib_target)
 
@@ -969,6 +1000,7 @@ def MakeInstallerAndroid(version, **kwargs):
 
 def MakeInstaller(version, **kwargs):
     target = GetTarget()
+
     if target == 'windows':
         dir = kwargs.pop('installdir', None)
         if dir is None:
@@ -991,8 +1023,10 @@ def MakeInstaller(version, **kwargs):
         if GetTargetArch() == 'x64':
             fn += '-x64'
 
+        compressor = kwargs.get('compressor')
+
         MakeInstallerNSIS(version, fn + '.exe', title, dir, **kwargs)
-        MakeDebugSymbolArchive(fn + '-pdb.zip', dir)
+        MakeDebugSymbolArchive(fn + '-pdb', compressor)
     elif target == 'linux':
         MakeInstallerLinux(version, **kwargs)
     elif target == 'darwin':

+ 117 - 44
makepanda/makepanda.py

@@ -21,7 +21,6 @@ try:
     import threading
     import signal
     import shutil
-    import sysconfig
     import plistlib
     import queue
 except KeyboardInterrupt:
@@ -94,7 +93,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "VRPN", "OPENSSL",                                   # Transport
   "FFTW",                                              # Algorithm helpers
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
-  "GTK2",                                              # GTK2 is used for PStats on Unix
+  "GTK3",                                              # GTK3 is used for PStats on Unix
   "MFC", "WX", "FLTK",                                 # Used for web plug-in only
   "COCOA",                                             # macOS toolkits
   "X11",                                               # Unix platform support
@@ -104,6 +103,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "PANDAPARTICLESYSTEM",                               # Built in particle system
   "CONTRIB",                                           # Experimental
   "SSE2", "NEON",                                      # Compiler features
+  "MIMALLOC",                                          # Memory allocators
 ])
 
 CheckPandaSourceTree()
@@ -429,6 +429,8 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile
     # This is manylinux1.  A bit of a sloppy check, though.
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux1-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux1-aarch64'
     else:
         PLATFORM = 'manylinux1-i686'
 
@@ -436,6 +438,8 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.12.so") or os.path.isfil
     # Same sloppy check for manylinux2010.
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2010-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux2010-aarch64'
     else:
         PLATFORM = 'manylinux2010-i686'
 
@@ -443,13 +447,17 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.17.so") or os.path.isfil
     # Same sloppy check for manylinux2014.
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2014-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux2014-aarch64'
     else:
         PLATFORM = 'manylinux2014-i686'
 
-elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64/libc-2.24.so")) and os.path.isdir("/opt/python"):
+elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so")) and os.path.isdir("/opt/python"):
     # Same sloppy check for manylinux_2_24.
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux_2_24-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux_2_24-aarch64'
     else:
         PLATFORM = 'manylinux_2_24-i686'
 
@@ -646,13 +654,19 @@ if (COMPILER == "MSVC"):
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "quartz.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbc32.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbccp32.lib")
+    if (PkgSkip("MIMALLOC")==0): LibName("MIMALLOC", GetThirdpartyDir() + "mimalloc/lib/mimalloc-static.lib")
     if (PkgSkip("OPENSSL")==0):
         if os.path.isfile(GetThirdpartyDir() + "openssl/lib/libpandassl.lib"):
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libpandassl.lib")
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libpandaeay.lib")
-        else:
+        elif os.path.isfile(GetThirdpartyDir() + "openssl/lib/ssleay32.lib"):
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libeay32.lib")
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/ssleay32.lib")
+        else:
+            LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libssl.lib")
+            LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libcrypto.lib")
+            LibName("OPENSSL", "crypt32.lib")
+            LibName("OPENSSL", "ws2_32.lib")
     if (PkgSkip("PNG")==0):
         if os.path.isfile(GetThirdpartyDir() + "png/lib/libpng16_static.lib"):
             LibName("PNG", GetThirdpartyDir() + "png/lib/libpng16_static.lib")
@@ -664,23 +678,39 @@ if (COMPILER == "MSVC"):
         else:
             LibName("TIFF", GetThirdpartyDir() + "tiff/lib/tiff.lib")
     if (PkgSkip("OPENEXR")==0):
-        suffix = ""
-        if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_2.lib"):
-            suffix = "-2_2"
-        elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_3.lib"):
-            suffix = "-2_3"
-        elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_4.lib"):
-            suffix = "-2_4"
-            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Imath" + suffix + ".lib")
-        if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + "_s.lib"):
-            suffix += "_s"  # _s suffix observed for OpenEXR 2.3 only so far
-        LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + ".lib")
-        LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread" + suffix + ".lib")
-        LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex" + suffix + ".lib")
-        if suffix == "-2_2":
-            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib")
+        if os.path.isfile(GetThirdpartyDir() + "openexr/lib/OpenEXRCore-3_1.lib"):
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/OpenEXR-3_1.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread-3_1.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Imath-3_1.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex-3_1.lib")
+        elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/OpenEXR-3_0.lib"):
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/OpenEXR-3_0.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread-3_0.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Imath-3_0.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex-3_0.lib")
+        elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/OpenEXR.lib"):
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/OpenEXR.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Imath.lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex.lib")
         else:
-            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib")
+            suffix = ""
+            if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_2.lib"):
+                suffix = "-2_2"
+            elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_3.lib"):
+                suffix = "-2_3"
+            elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_4.lib"):
+                suffix = "-2_4"
+                LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Imath" + suffix + ".lib")
+            if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + "_s.lib"):
+                suffix += "_s"  # _s suffix observed for OpenEXR 2.3 only so far
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + ".lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread" + suffix + ".lib")
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex" + suffix + ".lib")
+            if suffix == "-2_2":
+                LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib")
+            else:
+                LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib")
         IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/OpenEXR")
         IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/Imath")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
@@ -875,7 +905,6 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("OPENAL",    "openal",    ("openal"), "AL/al.h", framework = "OpenAL")
     SmartPkgEnable("SQUISH",    "",          ("squish"), "squish.h")
     SmartPkgEnable("TIFF",      "libtiff-4", ("tiff"), "tiff.h")
-    SmartPkgEnable("OPENEXR",   "OpenEXR",   ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), ("OpenEXR", "Imath", "OpenEXR/ImfOutputFile.h"))
     SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
     SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.h"))
     SmartPkgEnable("VORBIS",    "vorbisfile",("vorbisfile", "vorbis", "ogg"), ("ogg/ogg.h", "vorbis/vorbisfile.h"))
@@ -887,6 +916,7 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("SPIRV-TOOLS", "",        ("SPIRV-Tools", "SPIRV-Tools-opt"), "spirv-tools/optimizer.hpp")
     SmartPkgEnable("SPIRV-CROSS-GLSL", "",   ("spirv-cross-core", "spirv-cross-glsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
     SmartPkgEnable("SPIRV-CROSS-HLSL", "",   ("spirv-cross-core", "spirv-cross-hlsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
+    SmartPkgEnable("MIMALLOC",  "",          ("mimalloc"), "mimalloc.h")
 
     # Copy freetype libraries to be specified after harfbuzz libraries as well,
     # because there's a circular dependency between the two libraries.
@@ -905,6 +935,23 @@ if (COMPILER=="GCC"):
             for ffmpeg_lib in ffmpeg_libs:
                 LibName("FFMPEG", "-Wl,--exclude-libs,%s.a" % (ffmpeg_lib))
 
+    if not PkgSkip("OPENEXR"):
+        # OpenEXR libraries have different names depending on the version.
+        openexr_libdir = os.path.join(GetThirdpartyDir(), "openexr", "lib")
+        openexr_incs = ("OpenEXR", "Imath", "OpenEXR/ImfOutputFile.h")
+        if os.path.isfile(os.path.join(openexr_libdir, "libOpenEXR-3_1.a")):
+            SmartPkgEnable("OPENEXR", "", ("OpenEXR-3_1", "IlmThread-3_1", "Imath-3_1", "Iex-3_1"), openexr_incs)
+        if os.path.isfile(os.path.join(openexr_libdir, "libOpenEXR-3_0.a")):
+            SmartPkgEnable("OPENEXR", "", ("OpenEXR-3_0", "IlmThread-3_0", "Imath-3_0", "Iex-3_0"), openexr_incs)
+        elif os.path.isfile(os.path.join(openexr_libdir, "libOpenEXR.a")):
+            SmartPkgEnable("OPENEXR", "", ("OpenEXR", "IlmThread", "Imath", "Iex"), openexr_incs)
+        elif os.path.isfile(os.path.join(openexr_libdir, "libIlmImf.a")):
+            SmartPkgEnable("OPENEXR", "", ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), openexr_incs)
+        else:
+            # Find it in the system, preferably using pkg-config, otherwise
+            # using the OpenEXR 3 naming scheme.
+            SmartPkgEnable("OPENEXR", "OpenEXR", ("OpenEXR", "IlmThread", "Imath", "Iex"), openexr_incs)
+
     if GetTarget() != "darwin":
         for fcollada_lib in fcollada_libs:
             LibName("FCOLLADA", "-Wl,--exclude-libs,lib%s.a" % (fcollada_lib))
@@ -936,6 +983,9 @@ if (COMPILER=="GCC"):
             LibName("OPENEXR", "-Wl,--exclude-libs,libIlmImfUtil.a")
             LibName("OPENEXR", "-Wl,--exclude-libs,libIlmThread.a")
             LibName("OPENEXR", "-Wl,--exclude-libs,libImath.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libOpenEXR.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libOpenEXRCore.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libOpenEXRUtil.a")
 
         if not PkgSkip("VORBIS"):
             LibName("VORBIS", "-Wl,--exclude-libs,libogg.a")
@@ -956,6 +1006,9 @@ if (COMPILER=="GCC"):
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
 
+        if not PkgSkip("MIMALLOC"):
+            LibName("MIMALLOC", "-Wl,--exclude-libs,libmimalloc.a")
+
         LibName("GLSLANG", "-Wl,--exclude-libs,libglslang.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libSPIRV.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libOSDependent.a")
@@ -1010,7 +1063,7 @@ if (COMPILER=="GCC"):
 
     SmartPkgEnable("OPENSSL",   "openssl",   ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h"))
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
-    SmartPkgEnable("GTK2",      "gtk+-2.0")
+    SmartPkgEnable("GTK3",      "gtk+-3.0")
 
     if not PkgSkip("OPENSSL") and GetTarget() != "darwin":
         LibName("OPENSSL", "-Wl,--exclude-libs,libssl.a")
@@ -1025,11 +1078,6 @@ if (COMPILER=="GCC"):
     if GetHost() != "darwin":
         # Workaround for an issue where pkg-config does not include this path
         if GetTargetArch() in ("x86_64", "amd64"):
-            if (os.path.isdir("/usr/lib64/glib-2.0/include")):
-                IncDirectory("GTK2", "/usr/lib64/glib-2.0/include")
-            if (os.path.isdir("/usr/lib64/gtk-2.0/include")):
-                IncDirectory("GTK2", "/usr/lib64/gtk-2.0/include")
-
             if not PkgSkip("X11"):
                 if (os.path.isdir("/usr/X11R6/lib64")):
                     LibDirectory("ALWAYS", "/usr/X11R6/lib64")
@@ -1075,8 +1123,6 @@ if (COMPILER=="GCC"):
             LibName("FFMPEG", "-undefined dynamic_lookup")
         if not PkgSkip("ASSIMP"):
             LibName("ASSIMP", "-undefined dynamic_lookup")
-        if not PkgSkip("OPENEXR"):
-            LibName("OPENEXR", "-undefined dynamic_lookup")
         if not PkgSkip("VRPN"):
             LibName("VRPN", "-undefined dynamic_lookup")
 
@@ -1217,7 +1263,7 @@ def CompileCxx(obj,src,opts):
             cmd = "cl "
             if GetTargetArch() == 'x64':
                 cmd += "/favor:blend "
-            cmd += "/wd4996 /wd4275 /wd4273 "
+            cmd += "/wd4996 "
 
             # Set the minimum version to Windows Vista.
             cmd += "/DWINVER=0x600 "
@@ -1249,7 +1295,7 @@ def CompileCxx(obj,src,opts):
             if (building):
                 cmd += " /DBUILDING_" + building
 
-            if ("BIGOBJ" in opts) or GetTargetArch() == 'x64':
+            if ("BIGOBJ" in opts) or GetTargetArch() == 'x64' or not PkgSkip("EIGEN"):
                 cmd += " /bigobj"
 
             cmd += " /Zm300"
@@ -1267,7 +1313,7 @@ def CompileCxx(obj,src,opts):
             cmd = "icl "
             if GetTargetArch() == 'x64':
                 cmd += "/favor:blend "
-            cmd += "/wd4996 /wd4275 /wd4267 /wd4101 /wd4273 "
+            cmd += "/wd4996 /wd4267 /wd4101 "
             cmd += "/DWINVER=0x600 "
             cmd += "/Fo" + obj + " /c"
             for x in ipath: cmd += " /I" + x
@@ -1378,6 +1424,10 @@ def CompileCxx(obj,src,opts):
                 if 'NOARCH:' + arch.upper() not in opts:
                     cmd += " -arch %s" % arch
 
+        elif 'clang' not in GetCXX().split('/')[-1]:
+            # Enable interprocedural optimizations in GCC.
+            cmd += " -fno-semantic-interposition"
+
         if "SYSROOT" in SDK:
             if GetTarget() != "android":
                 cmd += ' --sysroot=%s' % (SDK["SYSROOT"])
@@ -1464,10 +1514,7 @@ def CompileCxx(obj,src,opts):
         if (optlevel==4): cmd += " -O3 -DNDEBUG"
 
         # Enable more warnings.
-        cmd += " -Wall -Wno-unused-function"
-
-        if not src.endswith(".c"):
-            cmd += " -Wno-reorder"
+        cmd += " -Wall -Wno-unused-function -Werror=return-type"
 
         # Ignore unused variables in NDEBUG builds, often used in asserts.
         if optlevel == 4:
@@ -2340,6 +2387,7 @@ DTOOL_CONFIG=[
     ("REPORT_OPENSSL_ERRORS",          '1',                      '1'),
     ("USE_PANDAFILESTREAM",            '1',                      '1'),
     ("USE_DELETED_CHAIN",              '1',                      '1'),
+    ("HAVE_MIMALLOC",                  'UNDEF',                  'UNDEF'),
     ("HAVE_WGL",                       '1',                      'UNDEF'),
     ("HAVE_DX9",                       'UNDEF',                  'UNDEF'),
     ("HAVE_THREADS",                   '1',                      '1'),
@@ -2482,6 +2530,20 @@ def WriteConfigSettings():
 
     dtool_config["HAVE_NET"] = '1'
 
+    if GetTarget() == 'windows':
+        if not PkgSkip("MIMALLOC"):
+            # This is faster than both DeletedBufferChain and malloc,
+            # especially in the multi-threaded case.
+            dtool_config["USE_MEMORY_MIMALLOC"] = '1'
+            dtool_config["USE_DELETED_CHAIN"] = 'UNDEF'
+        else:
+            # If we don't have mimalloc, use DeletedBufferChain as fallback,
+            # which is still more efficient than malloc.
+            dtool_config["USE_DELETED_CHAIN"] = '1'
+    else:
+        # On other systems, the default malloc seems to be fine.
+        dtool_config["USE_DELETED_CHAIN"] = 'UNDEF'
+
     if GetTarget() not in ("linux", "android"):
         dtool_config["HAVE_PROC_SELF_EXE"] = 'UNDEF'
         dtool_config["HAVE_PROC_SELF_MAPS"] = 'UNDEF'
@@ -3379,7 +3441,7 @@ if GetTarget() == 'windows':
 # DIRECTORY: dtool/src/dtoolbase/
 #
 
-OPTS=['DIR:dtool/src/dtoolbase', 'BUILDING:DTOOL']
+OPTS=['DIR:dtool/src/dtoolbase', 'BUILDING:DTOOL', 'MIMALLOC']
 TargetAdd('p3dtoolbase_composite1.obj', opts=OPTS, input='p3dtoolbase_composite1.cxx')
 TargetAdd('p3dtoolbase_composite2.obj', opts=OPTS, input='p3dtoolbase_composite2.cxx')
 TargetAdd('p3dtoolbase_lookup3.obj',    opts=OPTS, input='lookup3.c')
@@ -3410,7 +3472,7 @@ TargetAdd('libp3dtool.dll', input='p3dtoolbase_composite1.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_composite2.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_indent.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_lookup3.obj')
-TargetAdd('libp3dtool.dll', opts=['ADVAPI','WINSHELL','WINKERNEL'])
+TargetAdd('libp3dtool.dll', opts=['ADVAPI','WINSHELL','WINKERNEL','MIMALLOC'])
 
 #
 # DIRECTORY: dtool/src/cppparser/
@@ -5039,8 +5101,10 @@ if GetTarget() == 'android' and not PkgSkip("EGL") and not PkgSkip("GLES2"):
 # DIRECTORY: panda/src/tinydisplay/
 #
 
-if (GetTarget() in ('windows', 'darwin') or not PkgSkip("X11")) and not PkgSkip("TINYDISPLAY"):
+if not PkgSkip("TINYDISPLAY"):
     OPTS=['DIR:panda/src/tinydisplay', 'BUILDING:TINYDISPLAY', 'X11']
+    if not PkgSkip("X11"):
+        OPTS += ['X11']
     TargetAdd('p3tinydisplay_composite1.obj', opts=OPTS, input='p3tinydisplay_composite1.cxx')
     TargetAdd('p3tinydisplay_composite2.obj', opts=OPTS, input='p3tinydisplay_composite2.cxx')
     TargetAdd('p3tinydisplay_ztriangle_1.obj', opts=OPTS, input='ztriangle_1.cxx')
@@ -5051,7 +5115,7 @@ if (GetTarget() in ('windows', 'darwin') or not PkgSkip("X11")) and not PkgSkip(
     if GetTarget() == 'windows':
         TargetAdd('libp3tinydisplay.dll', input='libp3windisplay.dll')
         TargetAdd('libp3tinydisplay.dll', opts=['WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM'])
-    elif GetTarget() != 'darwin':
+    elif GetTarget() != 'darwin' and not PkgSkip("X11"):
         TargetAdd('libp3tinydisplay.dll', input='p3x11display_composite1.obj')
         TargetAdd('libp3tinydisplay.dll', opts=['X11'])
     TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite1.obj')
@@ -5912,19 +5976,19 @@ if not PkgSkip("PANDATOOL"):
 # DIRECTORY: pandatool/src/gtk-stats/
 #
 
-if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK2")):
+if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK3")):
     if GetTarget() == 'windows':
         OPTS=['DIR:pandatool/src/win-stats']
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='winstats_composite1.cxx')
     else:
-        OPTS=['DIR:pandatool/src/gtk-stats', 'GTK2']
+        OPTS=['DIR:pandatool/src/gtk-stats', 'GTK3']
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='gtkstats_composite1.cxx')
     TargetAdd('pstats.exe', input='pstats_composite1.obj')
     TargetAdd('pstats.exe', input='libp3pstatserver.lib')
     TargetAdd('pstats.exe', input='libp3progbase.lib')
     TargetAdd('pstats.exe', input='libp3pandatoolbase.lib')
     TargetAdd('pstats.exe', input=COMMON_PANDA_LIBS)
-    TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK2'])
+    TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK3'])
 
 #
 # DIRECTORY: pandatool/src/xfileprogs/
@@ -6060,17 +6124,21 @@ for VER in MAYAVERSIONS:
     TargetAdd('egg2maya'+VNUM+'.exe', opts=['ADVAPI']+ARCH_OPTS)
 
 if MAYA_BUILT:
+    OPTS=['DIR:pandatool/src/mayaprogs', 'DIR:pandatool/src/maya', 'DIR:pandatool/src/mayaegg', 'BUILDING:MISC', 'NOARCH:ARM64']
+
     TargetAdd('mayaprogs_mayaConversionClient.obj', opts=OPTS, input='mayaConversionClient.cxx')
 
     TargetAdd('maya2egg_mayaToEggClient.obj', opts=OPTS, input='mayaToEggClient.cxx')
     TargetAdd('maya2egg_client.exe', input='mayaprogs_mayaConversionClient.obj')
     TargetAdd('maya2egg_client.exe', input='maya2egg_mayaToEggClient.obj')
     TargetAdd('maya2egg_client.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('maya2egg_client.exe', opts=['NOARCH:ARM64'])
 
     TargetAdd('egg2maya_eggToMayaClient.obj', opts=OPTS, input='eggToMayaClient.cxx')
     TargetAdd('egg2maya_client.exe', input='mayaprogs_mayaConversionClient.obj')
     TargetAdd('egg2maya_client.exe', input='egg2maya_eggToMayaClient.obj')
     TargetAdd('egg2maya_client.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('egg2maya_client.exe', opts=['NOARCH:ARM64'])
 
 #
 # DIRECTORY: contrib/src/ai/
@@ -6128,7 +6196,7 @@ if PkgSkip("PYTHON") == 0:
 
     if GetTarget() == 'linux' or GetTarget() == 'freebsd':
         # Setup rpath so libs can be found in the same directory as the deployed game
-        LibName('DEPLOYSTUB', "-Wl,-rpath,\\$ORIGIN")
+        LibName('DEPLOYSTUB', "-Wl,--disable-new-dtags,-rpath,\\$ORIGIN")
         LibName('DEPLOYSTUB', "-Wl,-z,origin")
         LibName('DEPLOYSTUB', "-rdynamic")
 
@@ -6148,6 +6216,9 @@ if PkgSkip("PYTHON") == 0:
         PyTargetAdd('deploy-stubw.exe', input='deploy-stubw.obj')
         PyTargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'DEPLOYSTUB', 'NOICON'])
     elif GetTarget() == 'android':
+        TargetAdd('org/jnius/NativeInvocationHandler.class', opts=OPTS, input='NativeInvocationHandler.java')
+        TargetAdd('classes.dex', input='org/jnius/NativeInvocationHandler.class')
+
         PyTargetAdd('deploy-stubw_android_main.obj', opts=OPTS, input='android_main.cxx')
         PyTargetAdd('deploy-stubw_android_log.obj', opts=OPTS, input='android_log.c')
         PyTargetAdd('libdeploy-stubw.dll', input='android_native_app_glue.obj')
@@ -6276,6 +6347,8 @@ def ParallelMake(tasklist):
             tasklist = extras
         sys.stdout.flush()
         if tasksqueued == 0:
+            if len(tasklist) > 0:
+                continue
             break
         donetask = donequeue.get()
         if donetask == 0:

+ 45 - 11
makepanda/makepandacore.py

@@ -6,6 +6,7 @@
 ########################################################################
 
 import configparser
+from distutils import sysconfig # DO NOT CHANGE to sysconfig - see #1230
 import fnmatch
 import getpass
 import glob
@@ -17,7 +18,6 @@ import shutil
 import signal
 import subprocess
 import sys
-import sysconfig
 import threading
 import _thread as thread
 import time
@@ -572,6 +572,26 @@ def GetFlexVersion():
         Warn("Unable to detect flex version")
         return (0, 0, 0)
 
+SEVENZIP = None
+def GetSevenZip():
+    global SEVENZIP
+    if SEVENZIP is not None:
+        return SEVENZIP
+
+    win_util = os.path.join(GetThirdpartyBase(), 'win-util')
+    if GetHost() == 'windows' and os.path.isdir(win_util):
+        SEVENZIP = GetThirdpartyBase() + "/win-util/7za.exe"
+    elif LocateBinary('7z'):
+        SEVENZIP = '7z'
+    else:
+        # We don't strictly need it, so don't give an error
+        return None
+
+    return SEVENZIP
+
+def HasSevenZip():
+    return GetSevenZip() is not None
+
 ########################################################################
 ##
 ## LocateBinary
@@ -635,6 +655,9 @@ def oscmd(cmd, ignoreError = False, cwd=None):
 
         res = os.spawnl(os.P_WAIT, exe_path, cmd)
 
+        if res == -1073741510: # 0xc000013a
+            exit("keyboard interrupt")
+
         if cwd is not None:
             os.chdir(pwd)
     else:
@@ -717,7 +740,7 @@ def GetTimestamp(path):
     if path in TIMESTAMPCACHE:
         return TIMESTAMPCACHE[path]
     try:
-        date = os.path.getmtime(path)
+        date = int(os.path.getmtime(path))
     except:
         date = 0
     TIMESTAMPCACHE[path] = date
@@ -854,8 +877,11 @@ def JavaGetImports(path):
     imports = []
     try:
         for match in JavaImportRegex.finditer(source, 0):
-            impname = match.group(1)
-            imports.append(impname.strip())
+            impname = match.group(1).strip()
+            if not impname.startswith('java.') and \
+               not impname.startswith('dalvik.') and \
+               not impname.startswith('android.'):
+                imports.append(impname.strip())
     except:
         print("Failed to determine dependencies of \"" + path  +"\".")
         raise
@@ -871,7 +897,7 @@ def JavaGetImports(path):
 ##
 ########################################################################
 
-DCACHE_VERSION = 2
+DCACHE_VERSION = 3
 DCACHE_BACKED_UP = False
 
 def SaveDependencyCache():
@@ -1640,7 +1666,10 @@ def LocateLibrary(lib, lpath=[], prefer_static=False):
                 return os.path.join(dir, 'lib%s.a' % lib)
 
     for dir in lpath:
-        if target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
+        if target == 'windows':
+            if os.path.isfile(os.path.join(dir, lib + '.lib')):
+                return os.path.join(dir, lib + '.lib')
+        elif target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
             return os.path.join(dir, 'lib%s.dylib' % lib)
         elif target != 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.so' % lib)):
             return os.path.join(dir, 'lib%s.so' % lib)
@@ -2178,12 +2207,12 @@ def SdkLocatePython(prefer_thirdparty_python=False):
         LibDirectory("PYTHON", py_fwx + "/lib")
 
     #elif GetTarget() == 'windows':
-    #    SDK["PYTHON"] = os.path.dirname(sysconfig.get_path("include"))
+    #    SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc())
     #    SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
     #    SDK["PYTHONEXEC"] = sys.executable
 
     else:
-        SDK["PYTHON"] = sysconfig.get_path("include")
+        SDK["PYTHON"] = sysconfig.get_python_inc()
         SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags
         SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
 
@@ -3318,7 +3347,12 @@ def SetOrigExt(x, v):
     ORIG_EXT[x] = v
 
 def GetExtensionSuffix():
-    if CrossCompiling():
+    if GetTarget() == 'windows':
+        if GetTargetArch() == 'x64':
+            return '.cp%d%d-win_amd64.pyd' % (sys.version_info[:2])
+        else:
+            return '.cp%d%d-win32.pyd' % (sys.version_info[:2])
+    elif CrossCompiling():
         return '.{0}.so'.format(GetPythonABI())
     else:
         import _imp
@@ -3460,8 +3494,8 @@ def GetCurrentPythonVersionInfo():
         "soabi": GetPythonABI(),
         "ext_suffix": GetExtensionSuffix(),
         "executable": sys.executable,
-        "purelib": sysconfig.get_path("purelib"),
-        "platlib": sysconfig.get_path("platlib"),
+        "purelib": sysconfig.get_python_lib(False),
+        "platlib": sysconfig.get_python_lib(True),
     }
 
 

+ 21 - 1
makepanda/makewheel.py

@@ -562,6 +562,7 @@ class WheelFile(object):
             print("Adding {0} from {1}".format(target_path, orig_source_path))
 
         zinfo = zipfile.ZipInfo.from_file(source_path, target_path)
+        zinfo.compress_type = self.zip_file.compression
         if zinfo.date_time > self.max_date_time:
             zinfo.date_time = self.max_date_time
 
@@ -653,7 +654,7 @@ def makewheel(version, output_dir, platform=None):
                     platform = platform.replace("linux", "manylinux2010")
                 elif os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so"):
                     platform = platform.replace("linux", "manylinux2014")
-                elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64/libc-2.24.so"):
+                elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so"):
                     platform = platform.replace("linux", "manylinux_2_24")
 
     platform = platform.replace('-', '_').replace('.', '_')
@@ -785,6 +786,10 @@ if __debug__:
 
     for file in sorted(os.listdir(ext_mod_dir)):
         if file.endswith(ext_suffix):
+            if file.startswith('_tkinter.'):
+                # Tkinter is supplied in a separate wheel.
+                continue
+
             source_path = os.path.join(ext_mod_dir, file)
 
             if file.endswith('.pyd') and platform.startswith('cygwin'):
@@ -795,6 +800,21 @@ if __debug__:
 
             whl.write_file(target_path, source_path)
 
+    # Include the special sysconfigdata module.
+    if os.name == 'posix':
+        import sysconfig
+
+        if hasattr(sysconfig, '_get_sysconfigdata_name'):
+            modname = sysconfig._get_sysconfigdata_name() + '.py'
+        else:
+            modname = '_sysconfigdata.py'
+
+        for entry in sys.path:
+            source_path = os.path.join(entry, modname)
+            if os.path.isfile(source_path):
+                whl.write_file('deploy_libs/' + modname, source_path)
+                break
+
     # Add plug-ins.
     for lib in PLUGIN_LIBS:
         plugin_name = 'lib' + lib

+ 6 - 1
makepanda/test_wheel.py

@@ -37,7 +37,12 @@ def test_wheel(wheel, verbose=False):
         sys.exit(1)
 
     # Install pytest into the environment, as well as our wheel.
-    packages = ["pytest", wheel]
+    packages = [wheel]
+    if sys.version_info >= (3, 10):
+        packages += ["pytest>=6.2.4"]
+    else:
+        packages += ["pytest"]
+
     if sys.version_info[0:2] == (3, 4):
         if sys.platform == "win32":
             packages += ["colorama==0.4.1"]

BIN
models/plugin_images/auth_click.png


BIN
models/plugin_images/auth_ready.png


BIN
models/plugin_images/auth_rollover.png


BIN
models/plugin_images/download.png


Некоторые файлы не были показаны из-за большого количества измененных файлов