Browse Source

Merge branch 'master' into webgl-port

rdb 5 years ago
parent
commit
f04c2e0e0e
100 changed files with 1555 additions and 662 deletions
  1. 7 10
      .github/workflows/ci.yml
  2. 7 0
      .travis.yml
  3. 1 0
      BACKERS.md
  4. 29 8
      CMakeLists.txt
  5. 3 3
      README.md
  6. 12 2
      cmake/macros/PackageConfig.cmake
  7. 24 0
      cmake/modules/FindFFMPEG.cmake
  8. 3 8
      contrib/src/sceneeditor/SideWindow.py
  9. 0 1
      contrib/src/sceneeditor/collisionWindow.py
  10. 3 8
      contrib/src/sceneeditor/controllerWindow.py
  11. 1 7
      contrib/src/sceneeditor/dataHolder.py
  12. 3 7
      contrib/src/sceneeditor/lightingPanel.py
  13. 1 1
      contrib/src/sceneeditor/quad.py
  14. 2 12
      contrib/src/sceneeditor/sceneEditor.py
  15. 2 8
      contrib/src/sceneeditor/seAnimPanel.py
  16. 2 9
      contrib/src/sceneeditor/seBlendAnimPanel.py
  17. 1 0
      contrib/src/sceneeditor/seCameraControl.py
  18. 2 6
      contrib/src/sceneeditor/seColorEntry.py
  19. 0 1
      contrib/src/sceneeditor/seFileSaver.py
  20. 1 0
      contrib/src/sceneeditor/seGrid.py
  21. 0 1
      contrib/src/sceneeditor/seLights.py
  22. 3 2
      contrib/src/sceneeditor/seManipulation.py
  23. 6 10
      contrib/src/sceneeditor/seMopathRecorder.py
  24. 3 8
      contrib/src/sceneeditor/seParticlePanel.py
  25. 0 1
      contrib/src/sceneeditor/seParticles.py
  26. 3 8
      contrib/src/sceneeditor/sePlacer.py
  27. 3 8
      contrib/src/sceneeditor/seSceneGraphExplorer.py
  28. 2 2
      contrib/src/sceneeditor/seSession.py
  29. 4 8
      contrib/src/sceneeditor/seTree.py
  30. 1 3
      direct/src/dcparser/dcClass_ext.cxx
  31. 1 1
      direct/src/dcparser/dcPacker_ext.cxx
  32. 1 1
      direct/src/directtools/DirectUtil.py
  33. 79 2
      direct/src/dist/FreezeTool.py
  34. 112 182
      direct/src/dist/commands.py
  35. 173 0
      direct/src/dist/installers.py
  36. 1 4
      direct/src/dist/pefile.py
  37. 2 2
      direct/src/distributed/cConnectionRepository.cxx
  38. 3 2
      direct/src/extensions_native/NodePath_extensions.py
  39. 3 2
      direct/src/gui/OnscreenGeom.py
  40. 3 2
      direct/src/gui/OnscreenImage.py
  41. 113 12
      direct/src/gui/OnscreenText.py
  42. 0 38
      direct/src/showbase/showBase.cxx
  43. 0 5
      direct/src/showbase/showBase.h
  44. 37 11
      direct/src/stdpy/pickle.py
  45. 37 15
      direct/src/tkwidgets/EntryScale.py
  46. 28 0
      doc/ReleaseNotes
  47. 0 4
      dtool/Config.cmake
  48. 16 3
      dtool/LocalSetup.cmake
  49. 2 0
      dtool/dtool_config.h.in
  50. 1 1
      dtool/src/dtoolbase/neverFreeMemory.h
  51. 2 0
      dtool/src/dtoolutil/CMakeLists.txt
  52. 7 3
      dtool/src/dtoolutil/executionEnvironment.cxx
  53. 9 0
      dtool/src/dtoolutil/panda_getopt_impl.cxx
  54. 2 0
      dtool/src/dtoolutil/panda_getopt_impl.h
  55. 18 0
      dtool/src/interrogatedb/py_compat.cxx
  56. 23 5
      dtool/src/interrogatedb/py_compat.h
  57. 1 1
      dtool/src/interrogatedb/py_panda.cxx
  58. 1 1
      dtool/src/interrogatedb/py_wrappers.cxx
  59. 30 10
      makepanda/installer.nsi
  60. 3 1
      makepanda/makepackage.py
  61. 181 96
      makepanda/makepanda.py
  62. 34 24
      makepanda/makepandacore.py
  63. 1 1
      makepanda/makewheel.py
  64. 8 0
      panda/src/audio/audioSound.h
  65. 12 1
      panda/src/audiotraits/CMakeLists.txt
  66. 4 2
      panda/src/audiotraits/fmodAudioSound.cxx
  67. 6 4
      panda/src/audiotraits/openalAudioSound.cxx
  68. 3 0
      panda/src/cocoadisplay/CMakeLists.txt
  69. 28 12
      panda/src/collide/collisionPolygon_ext.cxx
  70. 1 1
      panda/src/collide/collisionPolygon_ext.h
  71. 22 12
      panda/src/device/evdevInputDevice.cxx
  72. 2 0
      panda/src/device/inputDeviceManager.cxx
  73. 8 0
      panda/src/display/config_display.cxx
  74. 1 0
      panda/src/display/config_display.h
  75. 8 0
      panda/src/display/graphicsPipe.I
  76. 22 0
      panda/src/display/graphicsPipe.cxx
  77. 5 0
      panda/src/display/graphicsPipe.h
  78. 197 0
      panda/src/display/graphicsStateGuardian.cxx
  79. 7 0
      panda/src/egg/eggTexture.cxx
  80. 1 0
      panda/src/egg/eggTexture.h
  81. 4 0
      panda/src/egg2pg/eggLoader.cxx
  82. 3 0
      panda/src/egg2pg/eggSaver.cxx
  83. 6 6
      panda/src/event/pythonTask.cxx
  84. 3 0
      panda/src/express/zipArchive.h
  85. 1 1
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  86. 1 1
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  87. 50 12
      panda/src/glstuff/glShaderContext_src.cxx
  88. 23 0
      panda/src/gobj/geom.cxx
  89. 2 0
      panda/src/gobj/geom.h
  90. 8 0
      panda/src/gobj/geomPrimitive.cxx
  91. 22 9
      panda/src/gobj/geomTristrips.cxx
  92. 8 8
      panda/src/gobj/geomVertexReader.I
  93. 4 4
      panda/src/gobj/geomVertexReader.h
  94. 8 8
      panda/src/gobj/geomVertexWriter.I
  95. 4 4
      panda/src/gobj/geomVertexWriter.h
  96. 9 0
      panda/src/gobj/shader.h
  97. 3 0
      panda/src/gobj/textureStage.cxx
  98. 2 0
      panda/src/gobj/textureStage.h
  99. 3 3
      panda/src/linmath/lvecBase2_ext_src.I
  100. 3 3
      panda/src/linmath/lvecBase3_ext_src.I

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

@@ -97,11 +97,8 @@ jobs:
         mv panda3d-1.10.8/thirdparty thirdparty
         rmdir panda3d-1.10.8
 
-        mkdir -p build/Frameworks
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework build/Frameworks/Cg.framework
-
-        mkdir -p "build/${{ matrix.config }}/Frameworks"
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework "build/${{ matrix.config }}/Frameworks/Cg.framework"
+        # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
+        install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
 
         brew install ccache
 
@@ -127,16 +124,16 @@ jobs:
       uses: actions/cache@v1
       with:
         path: thirdparty
-        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.7-r1
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.8-r3
     - name: Install dependencies (Windows)
       if: runner.os == 'Windows'
       shell: powershell
       run: |
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
           $wc = New-Object System.Net.WebClient
-          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip", "thirdparty-tools.zip")
           Expand-Archive -Path thirdparty-tools.zip
-          Move-Item -Path thirdparty-tools/panda3d-1.10.7/thirdparty -Destination .
+          Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
         }
 
     - name: ccache (non-Windows)
@@ -350,9 +347,9 @@ jobs:
       shell: powershell
       run: |
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.7/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       run: |

+ 7 - 0
.travis.yml

@@ -0,0 +1,7 @@
+language: cpp
+branches:
+  only:
+    - release/1.10.x
+    - release/1.9.x
+script:
+    - echo "Build disabled on master branch."

+ 1 - 0
BACKERS.md

@@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 * Sam Edwards
 * Max Voss
+* Hawkheart
 
 ## Enthusiasts
 

+ 29 - 8
CMakeLists.txt

@@ -32,23 +32,32 @@ a CMake < 3.9. Making a guess if this is a multi-config generator.")
   endif()
 endif()
 
-# Define the type of build we are setting up.
-set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
-if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
-  list(APPEND _configs Coverage)
-endif()
-
+# Set the default CMAKE_BUILD_TYPE before calling project().
 if(IS_MULTICONFIG)
   message(STATUS "Using multi-configuration generator")
 else()
-  # Set the default CMAKE_BUILD_TYPE before calling project().
   if(NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Standard CACHE STRING "Choose the type of build." FORCE)
     message(STATUS "Using default build type ${CMAKE_BUILD_TYPE}")
   else()
     message(STATUS "Using build type ${CMAKE_BUILD_TYPE}")
   endif()
-  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
+# Set defaults for macOS, must be before project().
+if(APPLE)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS version to target")
+  set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
+
+  if(CMAKE_VERSION VERSION_LESS "3.19" AND NOT CMAKE_OSX_SYSROOT)
+    # Older CMake chose SDK based on deployment target, against Apple's recommendations.
+    # However, we need to use the latest to be able to target arm64.
+    if(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk" CACHE STRING "")
+    elseif(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk" CACHE STRING "")
+    endif()
+  endif()
 endif()
 
 # Figure out the version
@@ -59,6 +68,18 @@ project(Panda3D VERSION ${_version})
 unset(_version)
 unset(_s)
 
+# Determine the possible build types.  Must be *after* calling project().
+set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
+if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
+  list(APPEND _configs Coverage)
+endif()
+
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "${_configs}" CACHE STRING "" FORCE)
+else()
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
 enable_testing()
 
 string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")

+ 3 - 3
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-7/).
+[this page](https://www.panda3d.org/download/sdk-1-10-8/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -64,8 +64,8 @@ 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.7/panda3d-1.10.7-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-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

+ 12 - 2
cmake/macros/PackageConfig.cmake

@@ -167,6 +167,12 @@ function(package_option name)
 
   set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
 
+  if(${found_as}_FOUND OR ${FOUND_AS}_FOUND)
+    set(PANDA_PACKAGE_FOUND_${name} ON PARENT_SCOPE)
+  else()
+    set(PANDA_PACKAGE_FOUND_${name} OFF PARENT_SCOPE)
+  endif()
+
   # Create the INTERFACE library used to depend on this package.
   add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
 
@@ -279,15 +285,19 @@ function(show_packages)
   foreach(package ${_ALL_CONFIG_PACKAGES})
     set(desc "${PANDA_PACKAGE_DESC_${package}}")
     set(note "${PANDA_PACKAGE_NOTE_${package}}")
-    if(HAVE_${package})
+
+    if(HAVE_${package} AND PANDA_PACKAGE_FOUND_${package})
       if(NOT note STREQUAL "")
         message("+ ${desc} (${note})")
       else()
         message("+ ${desc}")
       endif()
 
+    elseif(HAVE_${package})
+      message("! ${desc} (enabled but not found)")
+
     else()
-      if(NOT ${package}_FOUND)
+      if(NOT PANDA_PACKAGE_FOUND_${package})
         set(reason "not found")
       elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
         set(reason "not requested")

+ 24 - 0
cmake/modules/FindFFMPEG.cmake

@@ -89,16 +89,36 @@ if(APPLE)
   # When statically built for Apple, FFMPEG may have dependencies on these
   # additional frameworks and libraries.
 
+  find_library(APPLE_AUDIOTOOLBOX_LIBRARY AudioToolbox)
+  if(APPLE_AUDIOTOOLBOX_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_AUDIOTOOLBOX_LIBRARY}")
+  endif()
+
+  find_library(APPLE_COREMEDIA_LIBRARY CoreMedia)
+  if(APPLE_COREMEDIA_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_COREMEDIA_LIBRARY}")
+  endif()
+
   find_library(APPLE_COREVIDEO_LIBRARY CoreVideo)
   if(APPLE_COREVIDEO_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_COREVIDEO_LIBRARY}")
   endif()
 
+  find_library(APPLE_SECURITY_LIBRARY Security)
+  if(APPLE_SECURITY_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_SECURITY_LIBRARY}")
+  endif()
+
   find_library(APPLE_VDA_LIBRARY VideoDecodeAcceleration)
   if(APPLE_VDA_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_VDA_LIBRARY}")
   endif()
 
+  find_library(APPLE_VIDEOTOOLBOX_LIBRARY VideoToolbox)
+  if(APPLE_VIDEOTOOLBOX_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_VIDEOTOOLBOX_LIBRARY}")
+  endif()
+
   find_library(APPLE_ICONV_LIBRARY iconv)
   if(APPLE_ICONV_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_ICONV_LIBRARY}")
@@ -108,6 +128,10 @@ if(APPLE)
   if(APPLE_BZ2_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_BZ2_LIBRARY}")
   endif()
+
+  mark_as_advanced(APPLE_AUDIOTOOLBOX_LIBRARY APPLE_COREMEDIA_LIBRARY
+    APPLE_COREVIDEO_LIBRARY APPLE_SECURITY_LIBRARY APPLE_VDA_LIBRARY
+    APPLE_VIDEOTOOLBOX_LIBRARY APPLE_ICONV_LIBRARY APPLE_BZ2_LIBRARY)
 endif()
 
 mark_as_advanced(FFMPEG_LIBRARY_DIR)

+ 3 - 8
contrib/src/sceneeditor/SideWindow.py

@@ -7,14 +7,9 @@ from direct.tkwidgets.VectorWidgets import ColorEntry
 from direct.showbase.TkGlobal import spawnTkLoop
 import seSceneGraphExplorer
 
-import Pmw, sys
-
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, IntVar, Checkbutton, Toplevel
-    import tkinter
-else:
-    from Tkinter import Frame, IntVar, Checkbutton, Toplevel
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Frame, IntVar, Checkbutton, Toplevel
+import tkinter
 
 
 class sideWindow(AppShell):

+ 0 - 1
contrib/src/sceneeditor/collisionWindow.py

@@ -9,7 +9,6 @@ from seColorEntry import *
 from direct.tkwidgets import VectorWidgets
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
-import string, math, types
 from panda3d.core import *
 
 

+ 3 - 8
contrib/src/sceneeditor/controllerWindow.py

@@ -4,14 +4,9 @@
 #################################################################
 
 from direct.tkwidgets.AppShell import AppShell
-import sys, Pmw
-
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, Label, Button
-    import tkinter
-else:
-    from Tkinter import Frame, Label, Button
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Frame, Label, Button
+import tkinter
 
 # Define the Category
 KEYBOARD = 'Keyboard-'

+ 1 - 7
contrib/src/sceneeditor/dataHolder.py

@@ -5,12 +5,7 @@ from direct.showbase.TkGlobal import*
 import Pmw
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Floater
-
-if sys.version_info >= (3, 0):
-    from tkinter.filedialog import askopenfilename
-else:
-    from tkFileDialog import askopenfilename
-
+from tkinter.filedialog import askopenfilename
 
 #############################
 # Scene Editor Python Files #
@@ -27,7 +22,6 @@ from direct.actor import Actor
 # Core Python Modules         #
 ###############################
 import os
-import string
 import sys
 
 import seParticleEffect

+ 3 - 7
contrib/src/sceneeditor/lightingPanel.py

@@ -7,15 +7,11 @@ from direct.tkwidgets.AppShell import AppShell
 from seColorEntry import *
 from direct.tkwidgets.VectorWidgets import Vector3Entry
 from direct.tkwidgets.Slider import Slider
-import sys, math, types, Pmw
+import Pmw
 from panda3d.core import *
 
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, Button, Menubutton, Menu
-    import tkinter
-else:
-    from Tkinter import Frame, Button, Menubutton, Menu
-    import Tkinter as tkinter
+from tkinter import Frame, Button, Menubutton, Menu
+import tkinter
 
 
 class lightingPanel(AppShell):

+ 1 - 1
contrib/src/sceneeditor/quad.py

@@ -10,7 +10,7 @@ from direct.showbase.ShowBaseGlobal import *
 from direct.interval.IntervalGlobal import *
 from direct.showbase.DirectObject import DirectObject
 from panda3d.core import *
-import math
+from direct.task import Task
 
 
 class ViewPort:

+ 2 - 12
contrib/src/sceneeditor/sceneEditor.py

@@ -8,13 +8,8 @@ from direct.showbase.ShowBase import ShowBase
 ShowBase()
 
 from direct.showbase.TkGlobal import spawnTkLoop
-
-if sys.version_info >= (3, 0):
-    from tkinter import *
-    from tkinter.filedialog import *
-else:
-    from Tkinter import *
-    from tkFileDialog import *
+from tkinter import *
+from tkinter.filedialog import *
 
 from direct.directtools.DirectGlobals import *
 from direct.tkwidgets.AppShell import*
@@ -36,17 +31,12 @@ from seBlendAnimPanel import *
 from controllerWindow import *
 from AlignTool import *
 
-
-
-import os
-import string
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
 from direct.actor import Actor
 import seAnimPanel
 from direct.task import Task
-import math
 
 #################################################################
 # All scene and windows object will be stored in here.

+ 2 - 8
contrib/src/sceneeditor/seAnimPanel.py

@@ -5,15 +5,9 @@
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
-import string
-import math
-import types
 from direct.task import Task
 
-if sys.version_info >= (3, 0):
-    from tkinter.simpledialog import askfloat
-else:
-    from tkSimpleDialog import askfloat
+from tkinter.simpledialog import askfloat
 
 FRAMES = 0
 SECONDS = 1
@@ -407,7 +401,7 @@ class AnimPanel(AppShell):
         #################################################################
         if self.animName in self['animList']:
             # Convert scale value to float
-            frame = string.atof(frame)
+            frame = float(frame)
             # Now convert t to seconds for offset calculations
             if self.unitsVar.get() == FRAMES:
                 frame = frame / self.fps

+ 2 - 9
contrib/src/sceneeditor/seBlendAnimPanel.py

@@ -5,15 +5,8 @@
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
-import string
-import math
-import types
 from direct.task import Task
-
-if sys.version_info >= (3, 0):
-    from tkinter.simpledialog import askfloat
-else:
-    from tkSimpleDialog import askfloat
+from tkinter.simpledialog import askfloat
 
 FRAMES = 0
 SECONDS = 1
@@ -431,7 +424,7 @@ class BlendAnimPanel(AppShell):
         #################################################################
         if (self.animNameA in self['animList'])and(self.animNameB in self['animList']):
             # Convert scale value to float
-            frame = string.atof(frame)
+            frame = float(frame)
             # Now convert t to seconds for offset calculations
             if self.unitsVar.get() == FRAMES:
                 frame = frame / self.fps

+ 1 - 0
contrib/src/sceneeditor/seCameraControl.py

@@ -16,6 +16,7 @@ from direct.directtools.DirectUtil import *
 from seGeometry import *
 from direct.directtools.DirectGlobals import *
 from direct.task import Task
+import math
 
 CAM_MOVE_DURATION = 1.2
 COA_MARKER_SF = 0.0075

+ 2 - 6
contrib/src/sceneeditor/seColorEntry.py

@@ -12,13 +12,9 @@
 from direct.tkwidgets import Valuator
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
-import sys, Pmw
+import Pmw
 from direct.tkwidgets.VectorWidgets import VectorEntry
-
-if sys.version_info >= (3, 0):
-    from tkinter.colorchooser import askcolor
-else:
-    from tkColorChooser import askcolor
+from tkinter.colorchooser import askcolor
 
 
 class seColorEntry(VectorEntry):

+ 0 - 1
contrib/src/sceneeditor/seFileSaver.py

@@ -8,7 +8,6 @@ from panda3d.core import *
 from direct.showbase.ShowBaseGlobal import *
 import os
 import shutil
-import string
 
 ####################################################################################################################################################
 #### These modules are modified versions of Disney's equivalent modules

+ 1 - 0
contrib/src/sceneeditor/seGrid.py

@@ -14,6 +14,7 @@
 from direct.showbase.DirectObject import *
 from direct.directtools.DirectUtil import *
 from seGeometry import *
+import math
 
 class DirectGrid(NodePath,DirectObject):
     def __init__(self):

+ 0 - 1
contrib/src/sceneeditor/seLights.py

@@ -5,7 +5,6 @@
 from direct.showbase.DirectObject import *
 from direct.directtools import DirectUtil
 from panda3d.core import *
-import string
 
 
 class seLight(NodePath):

+ 3 - 2
contrib/src/sceneeditor/seManipulation.py

@@ -17,6 +17,7 @@ from direct.directtools.DirectGlobals import *
 from direct.directtools.DirectUtil import *
 from seGeometry import *
 from direct.task import Task
+import math
 
 class DirectManipulationControl(DirectObject):
     def __init__(self):
@@ -601,7 +602,7 @@ class ObjectHandles(NodePath,DirectObject):
         self.reparentTo(hidden)
 
     def enableHandles(self, handles):
-        if type(handles) == types.ListType:
+        if type(handles) is list:
             for handle in handles:
                 self.enableHandle(handle)
         elif handles == 'x':
@@ -642,7 +643,7 @@ class ObjectHandles(NodePath,DirectObject):
             self.zDiscGroup.reparentTo(self.zHandles)
 
     def disableHandles(self, handles):
-        if type(handles) == types.ListType:
+        if type(handles) is list:
             for handle in handles:
                 self.disableHandle(handle)
         elif handles == 'x':

+ 6 - 10
contrib/src/sceneeditor/seMopathRecorder.py

@@ -25,16 +25,12 @@ from direct.tkwidgets.Slider import Slider
 from direct.tkwidgets.EntryScale import EntryScale
 from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
 from direct.tkwidgets.VectorWidgets import ColorEntry
-import os, string, sys, Pmw
+import os, Pmw
+import math
 
-if sys.version_info >= (3, 0):
-    from tkinter import Button, Frame, Radiobutton, Checkbutton, Label
-    from tkinter import StringVar, BooleanVar, Entry, Scale
-    import tkinter
-else:
-    from Tkinter import Button, Frame, Radiobutton, Checkbutton, Label
-    from Tkinter import StringVar, BooleanVar, Entry, Scale
-    import Tkinter as tkinter
+from tkinter import Button, Frame, Radiobutton, Checkbutton, Label
+from tkinter import StringVar, BooleanVar, Entry, Scale
+import tkinter
 
 
 PRF_UTILITIES = [
@@ -374,7 +370,7 @@ class MopathRecorder(AppShell, DirectObject):
         self.speedEntry.bind(
             '<Return>',
             lambda e = None, s = self: s.setSpeedScale(
-            string.atof(s.speedVar.get())))
+            float(s.speedVar.get())))
         self.speedEntry.pack(side = tkinter.LEFT, expand = 0)
         frame.pack(fill = tkinter.X, expand = 1)
 

+ 3 - 8
contrib/src/sceneeditor/seParticlePanel.py

@@ -2,7 +2,7 @@
 
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import AppShell
-import os, Pmw, sys
+import os, Pmw
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Floater import Floater
 from direct.tkwidgets.Slider import Slider
@@ -13,13 +13,8 @@ import seForceGroup
 import seParticles
 import seParticleEffect
 
-
-if sys.version_info >= (3, 0):
-    from tkinter.filedialog import *
-    from tkinter.simpledialog import askstring
-else:
-    from tkFileDialog import *
-    from tkSimpleDialog import askstring
+from tkinter.filedialog import *
+from tkinter.simpledialog import askstring
 
 
 class ParticlePanel(AppShell):

+ 0 - 1
contrib/src/sceneeditor/seParticles.py

@@ -3,7 +3,6 @@ from panda3d.physics import *
 from direct.particles.ParticleManagerGlobal import *
 from direct.showbase.PhysicsManagerGlobal import *
 #import OrientedParticleFactory
-import string
 import os
 from direct.directnotify import DirectNotifyGlobal
 import sys

+ 3 - 8
contrib/src/sceneeditor/sePlacer.py

@@ -6,14 +6,9 @@ from direct.tkwidgets.AppShell import AppShell
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Floater import Floater
 from panda3d.core import *
-import sys, Pmw
-
-if sys.version_info >= (3, 0):
-    from tkinter import Button, Menubutton, Menu, StringVar
-    import tkinter
-else:
-    from Tkinter import Button, Menubutton, Menu, StringVar
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Button, Menubutton, Menu, StringVar
+import tkinter
 
 """
 TODO:

+ 3 - 8
contrib/src/sceneeditor/seSceneGraphExplorer.py

@@ -11,14 +11,9 @@
 from direct.showbase.DirectObject import DirectObject
 from seTree import TreeNode, TreeItem
 
-import Pmw, sys
-
-if sys.version_info >= (3, 0):
-    from tkinter import IntVar, Frame, Label
-    import tkinter
-else:
-    from Tkinter import IntVar, Frame, Label
-    import Tkinter as tkinter
+import Pmw
+from tkinter import IntVar, Frame, Label
+import tkinter
 
 # changing these strings requires changing sceneEditor.py SGE_ strs too!
 # This list of items will be showed on the pop out window when user right click on

+ 2 - 2
contrib/src/sceneeditor/seSession.py

@@ -20,9 +20,9 @@ from seGeometry import *
 from direct.tkpanels import Placer
 from direct.tkwidgets import Slider
 from direct.gui import OnscreenText
-import types
-import string
+from direct.task import Task
 from direct.showbase import Loader
+import math
 
 class SeSession(DirectObject):  ### Customized DirectSession
 

+ 4 - 8
contrib/src/sceneeditor/seTree.py

@@ -12,16 +12,12 @@
 #
 #################################################################
 
-import os, sys, string, Pmw
+import os, Pmw
 from direct.showbase.DirectObject import DirectObject
 from panda3d.core import *
 
-if sys.version_info >= (3, 0):
-    import tkinter
-    from tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
-else:
-    import Tkinter as tkinter
-    from Tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
+import tkinter
+from tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
 
 # Initialize icon directory
 ICONDIR = getModelPath().findFile(Filename('icons')).toOsSpecific()
@@ -221,7 +217,7 @@ class TreeNode:
             self.children[key] = child
             self.kidKeys.append(key)
         # Remove unused children
-        for key in self.children.keys():
+        for key in list(self.children.keys()):
             if key not in self.kidKeys:
                 del(self.children[key])
         cx = x+20

+ 1 - 3
direct/src/dcparser/dcClass_ext.cxx

@@ -400,9 +400,7 @@ pack_required_field(DCPacker &packer, PyObject *distobj,
     PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
   nassertr(func != nullptr, false);
 
-  PyObject *empty_args = PyTuple_New(0);
-  PyObject *result = PyObject_CallObject(func, empty_args);
-  Py_DECREF(empty_args);
+  PyObject *result = PyObject_CallNoArgs(func);
   Py_DECREF(func);
   if (result == nullptr) {
     // We don't set this as an exception, since presumably the Python method

+ 1 - 1
direct/src/dcparser/dcPacker_ext.cxx

@@ -309,7 +309,7 @@ unpack_class_object(const DCClass *dclass) {
   if (!dclass->has_constructor()) {
     // If the class uses a default constructor, go ahead and create the Python
     // object for it now.
-    object = PyObject_CallObject(class_def, nullptr);
+    object = PyObject_CallNoArgs(class_def);
     if (object == nullptr) {
       return nullptr;
     }

+ 1 - 1
direct/src/directtools/DirectUtil.py

@@ -19,7 +19,7 @@ def getTkColorString(color):
     Print out a Tk compatible version of a color string
     """
     def toHex(intVal):
-        val = int(round(intVal))
+        val = int(intVal)
         if val < 16:
             return "0" + hex(val)[2:]
         else:

+ 79 - 2
direct/src/dist/FreezeTool.py

@@ -211,7 +211,7 @@ class CompilationEnvironment:
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
 
         elif self.platform.startswith('osx_'):
-            # OSX
+            # macOS
             proc = self.platform.split('_', 1)[1]
             if proc == 'i386':
                 self.arch = '-arch i386'
@@ -219,6 +219,8 @@ class CompilationEnvironment:
                 self.arch = '-arch ppc'
             elif proc == 'amd64':
                 self.arch = '-arch x86_64'
+            elif proc in ('arm64', 'aarch64'):
+                self.arch = '-arch arm64'
             self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
@@ -636,7 +638,9 @@ okMissing = [
     'EasyDialogs', 'SOCKS', 'ic', 'rourl2path', 'termios', 'vms_lib',
     'OverrideFrom23._Res', 'email', 'email.Utils', 'email.Generator',
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
-    'direct.extensions_native.extensions_darwin',
+    'direct.extensions_native.extensions_darwin', '_manylinux',
+    'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
+    'collections.Sequence', 'numpy_distutils',
     ]
 
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -2498,6 +2502,9 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         except ImportError as msg:
             self.msg(2, "ImportError:", str(msg))
             self._add_badmodule(name, caller)
+        except SyntaxError as msg:
+            self.msg(2, "SyntaxError:", str(msg))
+            self._add_badmodule(name, caller)
         else:
             if fromlist:
                 for sub in fromlist:
@@ -2511,6 +2518,76 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                         self.msg(2, "ImportError:", str(msg))
                         self._add_badmodule(fullname, caller)
 
+    def scan_code(self, co, m):
+        code = co.co_code
+        # This was renamed to scan_opcodes in Python 3.6
+        if hasattr(self, 'scan_opcodes_25'):
+            scanner = self.scan_opcodes_25
+        else:
+            scanner = self.scan_opcodes
+
+        for what, args in scanner(co):
+            if what == "store":
+                name, = args
+                m.globalnames[name] = 1
+            elif what in ("import", "absolute_import"):
+                fromlist, name = args
+                have_star = 0
+                if fromlist is not None:
+                    if "*" in fromlist:
+                        have_star = 1
+                    fromlist = [f for f in fromlist if f != "*"]
+                if what == "absolute_import": level = 0
+                else: level = -1
+                self._safe_import_hook(name, m, fromlist, level=level)
+                if have_star:
+                    # We've encountered an "import *". If it is a Python module,
+                    # the code has already been parsed and we can suck out the
+                    # global names.
+                    mm = None
+                    if m.__path__:
+                        # At this point we don't know whether 'name' is a
+                        # submodule of 'm' or a global module. Let's just try
+                        # the full name first.
+                        mm = self.modules.get(m.__name__ + "." + name)
+                    if mm is None:
+                        mm = self.modules.get(name)
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            elif what == "relative_import":
+                level, fromlist, name = args
+                parent = self.determine_parent(m, level=level)
+                if name:
+                    self._safe_import_hook(name, m, fromlist, level=level)
+                else:
+                    self._safe_import_hook(parent.__name__, None, fromlist, level=0)
+
+                if fromlist and "*" in fromlist:
+                    if name:
+                        mm = self.modules.get(parent.__name__ + "." + name)
+                    else:
+                        mm = self.modules.get(parent.__name__)
+
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            else:
+                # We don't expect anything else from the generator.
+                raise RuntimeError(what)
+
+        for c in co.co_consts:
+            if isinstance(c, type(co)):
+                self.scan_code(c, m)
+
     def find_module(self, name, path=None, parent=None):
         """ Finds a module with the indicated name on the given search path
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where

+ 112 - 182
direct/src/dist/commands.py

@@ -7,6 +7,7 @@ on how to use these commands.
 import collections
 import os
 import plistlib
+import pkg_resources
 import sys
 import subprocess
 import zipfile
@@ -24,20 +25,11 @@ import distutils.log
 
 from . import FreezeTool
 from . import pefile
+from . import installers
 from .icon import Icon
 import panda3d.core as p3d
 
 
-if sys.version_info < (3, 0):
-    # Warn the user.  They might be using Python 2 by accident.
-    print("=================================================================")
-    print("WARNING: You are using Python 2, which has reached the end of its")
-    print("WARNING: life as of January 1, 2020.  Please upgrade to Python 3.")
-    print("=================================================================")
-    sys.stdout.flush()
-    time.sleep(4.0)
-
-
 def _parse_list(input):
     if isinstance(input, str):
         input = input.strip().replace(',', '\n')
@@ -213,7 +205,8 @@ class build_apps(setuptools.Command):
             'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll',
             'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll',
             'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll',
-            'setupapi.dll', 'version.dll',
+            'setupapi.dll', 'version.dll', 'userenv.dll', 'netapi32.dll',
+            'crypt32.dll', 'bcrypt.dll',
 
             # manylinux1/linux
             'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*',
@@ -229,14 +222,45 @@ class build_apps(setuptools.Command):
             '/usr/lib/libSystem.*.dylib',
             '/usr/lib/libbz2.*.dylib',
             '/usr/lib/libedit.*.dylib',
+            '/usr/lib/libffi.dylib',
+            '/usr/lib/libauditd.0.dylib',
+            '/usr/lib/libgermantok.dylib',
+            '/usr/lib/liblangid.dylib',
+            '/usr/lib/libarchive.2.dylib',
+            '/usr/lib/libipsec.A.dylib',
+            '/usr/lib/libpanel.5.4.dylib',
+            '/usr/lib/libiodbc.2.1.18.dylib',
+            '/usr/lib/libhunspell-1.2.0.0.0.dylib',
+            '/usr/lib/libsqlite3.dylib',
+            '/usr/lib/libpam.1.dylib',
+            '/usr/lib/libtidy.A.dylib',
+            '/usr/lib/libDHCPServer.A.dylib',
+            '/usr/lib/libpam.2.dylib',
+            '/usr/lib/libXplugin.1.dylib',
+            '/usr/lib/libxslt.1.dylib',
+            '/usr/lib/libiodbcinst.2.1.18.dylib',
+            '/usr/lib/libBSDPClient.A.dylib',
+            '/usr/lib/libsandbox.1.dylib',
+            '/usr/lib/libform.5.4.dylib',
+            '/usr/lib/libbsm.0.dylib',
+            '/usr/lib/libMatch.1.dylib',
+            '/usr/lib/libresolv.9.dylib',
+            '/usr/lib/libcharset.1.dylib',
+            '/usr/lib/libxml2.2.dylib',
+            '/usr/lib/libiconv.2.dylib',
+            '/usr/lib/libScreenReader.dylib',
+            '/usr/lib/libdtrace.dylib',
+            '/usr/lib/libicucore.A.dylib',
+            '/usr/lib/libsasl2.2.dylib',
+            '/usr/lib/libpcap.A.dylib',
+            '/usr/lib/libexslt.0.dylib',
+            '/usr/lib/libcurl.4.dylib',
+            '/usr/lib/libncurses.5.4.dylib',
+            '/usr/lib/libxar.1.dylib',
+            '/usr/lib/libmenu.5.4.dylib',
             '/System/Library/**',
         ]
 
-        if sys.version_info >= (3, 5):
-            # Python 3.5+ requires at least Windows Vista to run anyway, so we
-            # shouldn't warn about DLLs that are shipped with Vista.
-            self.exclude_dependencies += ['bcrypt.dll']
-
         self.package_data_dirs = {}
         self.hidden_imports = {}
 
@@ -371,7 +395,8 @@ class build_apps(setuptools.Command):
             abi_tag += 'm'
 
         whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
-        os.makedirs(whldir, exist_ok=True)
+        if not os.path.isdir(whldir):
+            os.makedirs(whldir)
 
         # Remove any .zip files. These are built from a VCS and block for an
         # interactive prompt on subsequent downloads.
@@ -491,6 +516,7 @@ class build_apps(setuptools.Command):
 
         path = sys.path[:]
         p3dwhl = None
+        wheelpaths = []
 
         if use_wheels:
             wheelpaths = self.download_wheels(platform)
@@ -769,6 +795,7 @@ class build_apps(setuptools.Command):
         for module, source_path in freezer_extras:
             if source_path is not None:
                 # Rename panda3d/core.pyd to panda3d.core.pyd
+                source_path = os.path.normpath(source_path)
                 basename = os.path.basename(source_path)
                 if '.' in module:
                     basename = module.rsplit('.', 1)[0] + '.' + basename
@@ -778,6 +805,20 @@ class build_apps(setuptools.Command):
                 if len(parts) >= 3 and '-' in parts[-2]:
                     parts = parts[:-2] + parts[-1:]
                     basename = '.'.join(parts)
+
+                # Was this not found in a wheel?  Then we may have a problem,
+                # since it may be for the current platform instead of the target
+                # platform.
+                if use_wheels:
+                    found_in_wheel = False
+                    for whl in wheelpaths:
+                        whl = os.path.normpath(whl)
+                        if source_path.lower().startswith(os.path.join(whl, '').lower()):
+                            found_in_wheel = True
+                            break
+
+                    if not found_in_wheel:
+                        self.warn('{} was not found in any downloaded wheel, is a dependency missing from requirements.txt?'.format(basename))
             else:
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 if module in whl_modules:
@@ -881,6 +922,27 @@ class build_apps(setuptools.Command):
             return check_pattern(fname, include_copy_list) and \
                 not check_pattern(fname, ignore_copy_list)
 
+        def skip_directory(src):
+            # Provides a quick-out for directory checks.  NOT recursive.
+            fn = p3d.Filename.from_os_specific(os.path.normpath(src))
+            path = fn.get_fullpath()
+            fn.make_absolute()
+            abspath = fn.get_fullpath()
+
+            for pattern in ignore_copy_list:
+                if not pattern.pattern.endswith('/*') and \
+                   not pattern.pattern.endswith('/**'):
+                    continue
+
+                pattern_dir = p3d.Filename(pattern.pattern).get_dirname()
+                if abspath.startswith(pattern_dir + '/'):
+                    return True
+
+                if path.startswith(pattern_dir + '/'):
+                    return True
+
+            return False
+
         def copy_file(src, dst):
             src = os.path.normpath(src)
             dst = os.path.normpath(dst)
@@ -921,6 +983,10 @@ class build_apps(setuptools.Command):
         rootdir = os.getcwd()
         for dirname, subdirlist, filelist in os.walk(rootdir):
             dirpath = os.path.relpath(dirname, rootdir)
+            if skip_directory(dirpath):
+                self.announce('skipping directory {}'.format(dirpath))
+                continue
+
             for fname in filelist:
                 src = os.path.join(dirpath, fname)
                 dst = os.path.join(builddir, update_path(src))
@@ -1249,6 +1315,14 @@ class bdist_apps(setuptools.Command):
         # Everything else defaults to ['zip']
     }
 
+    DEFAULT_INSTALLER_FUNCS = {
+        'zip': installers.create_zip,
+        'gztar': installers.create_gztar,
+        'bztar': installers.create_bztar,
+        'xztar': installers.create_xztar,
+        'nsis': installers.create_nsis,
+    }
+
     description = 'bundle built Panda3D applications into distributable forms'
     user_options = build_apps.user_options + [
         ('dist-dir=', 'd', 'directory to put final built distributions in'),
@@ -1262,6 +1336,8 @@ class bdist_apps(setuptools.Command):
         self.installers = {}
         self.dist_dir = os.path.join(os.getcwd(), 'dist')
         self.skip_build = False
+        self.installer_functions = {}
+        self._current_platform = None
         for opt in self._build_apps_options():
             setattr(self, opt, None)
 
@@ -1273,145 +1349,19 @@ class bdist_apps(setuptools.Command):
             for key, value in _parse_dict(self.installers).items()
         }
 
-    def _get_archive_basedir(self):
-        return self.distribution.get_name()
+        tmp = self.DEFAULT_INSTALLER_FUNCS.copy()
+        tmp.update(self.installer_functions)
+        tmp.update({
+            entrypoint.name: entrypoint.load()
+            for entrypoint in pkg_resources.iter_entry_points('panda3d.bdist_apps.installers')
+        })
+        self.installer_functions = tmp
 
-    def create_zip(self, basename, build_dir):
-        import zipfile
-
-        base_dir = self._get_archive_basedir()
-
-        with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
-            zf.write(build_dir, base_dir)
-
-            for dirpath, dirnames, filenames in os.walk(build_dir):
-                for name in sorted(dirnames):
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    zf.write(path, path.replace(build_dir, base_dir, 1))
-                for name in filenames:
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    if os.path.isfile(path):
-                        zf.write(path, path.replace(build_dir, base_dir, 1))
-
-    def create_tarball(self, basename, build_dir, tar_compression):
-        import tarfile
-
-        base_dir = self._get_archive_basedir()
-        build_cmd = self.get_finalized_command('build_apps')
-        binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
-
-        def tarfilter(tarinfo):
-            if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
-                tarinfo.mode = 0o755
-            else:
-                tarinfo.mode = 0o644
-            return tarinfo
-
-        with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
-            tf.add(build_dir, base_dir, filter=tarfilter)
-
-    def create_nsis(self, basename, build_dir, is_64bit):
-        # Get a list of build applications
-        build_cmd = self.get_finalized_command('build_apps')
-        apps = build_cmd.gui_apps.copy()
-        apps.update(build_cmd.console_apps)
-        apps = [
-            '{}.exe'.format(i)
-            for i in apps
-        ]
-
-        shortname = self.distribution.get_name()
-
-        # Create the .nsi installer script
-        nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
-        nsifile.unlink()
-        nsi = open(nsifile.to_os_specific(), "w")
+    def get_archive_basedir(self):
+        return self.distribution.get_name()
 
-        # Some global info
-        nsi.write('Name "%s"\n' % shortname)
-        nsi.write('OutFile "%s"\n' % os.path.join(self.dist_dir, basename+'.exe'))
-        if is_64bit:
-            nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
-        else:
-            nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
-        nsi.write('SetCompress auto\n')
-        nsi.write('SetCompressor lzma\n')
-        nsi.write('ShowInstDetails nevershow\n')
-        nsi.write('ShowUninstDetails nevershow\n')
-        nsi.write('InstType "Typical"\n')
-
-        # Tell Vista that we require admin rights
-        nsi.write('RequestExecutionLevel admin\n')
-        nsi.write('\n')
-
-        # TODO offer run and desktop shortcut after we figure out how to deal
-        # with multiple apps
-
-        nsi.write('!include "MUI2.nsh"\n')
-        nsi.write('!define MUI_ABORTWARNING\n')
-        nsi.write('\n')
-        nsi.write('Var StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
-        # TODO license file
-        nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
-        nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_PAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
-        nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
-        nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
-
-        # This section defines the installer.
-        nsi.write('Section "" SecCore\n')
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        curdir = ""
-        nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
-        build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
-        for root, dirs, files in os.walk(build_dir):
-            for name in files:
-                basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
-                file = p3d.Filename(basefile)
-                file.makeAbsolute()
-                file.makeRelativeTo(nsi_dir)
-                outdir = p3d.Filename(basefile)
-                outdir.makeAbsolute()
-                outdir.makeRelativeTo(build_root_dir)
-                outdir = outdir.getDirname().replace('/', '\\')
-                if curdir != outdir:
-                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
-                    curdir = outdir
-                nsi.write('  File "%s"\n' % (file.toOsSpecific()))
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
-        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
-        for app in apps:
-            nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
-        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
-        nsi.write('SectionEnd\n')
-
-        # This section defines the uninstaller.
-        nsi.write('Section Uninstall\n')
-        nsi.write('  RMDir /r "$INSTDIR"\n')
-        nsi.write('  ; Desktop icon\n')
-        nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
-        nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
-        nsi.write('SectionEnd\n')
-        nsi.close()
-
-        cmd = ['makensis']
-        for flag in ["V2"]:
-            cmd.append(
-                '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
-            )
-        cmd.append(nsifile.to_os_specific())
-        subprocess.check_call(cmd)
+    def get_current_platform(self):
+        return self._current_platform
 
     def run(self):
         build_cmd = self.distribution.get_command_obj('build_apps')
@@ -1433,35 +1383,15 @@ class bdist_apps(setuptools.Command):
             build_dir = os.path.join(build_base, platform)
             basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
             installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
+            self._current_platform = platform
 
             for installer in installers:
                 self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
+                if installer not in self.installer_functions:
+                    self.announce(
+                        '\tUnknown installer: {}'.format(installer),
+                        distutils.log.ERROR
+                    )
+                    continue
 
-                if installer == 'zip':
-                    self.create_zip(basename, build_dir)
-                elif installer in ('gztar', 'bztar', 'xztar'):
-                    compress = installer.replace('tar', '')
-                    if compress == 'bz':
-                        compress = 'bz2'
-
-                    self.create_tarball(basename, build_dir, compress)
-                elif installer == 'nsis':
-                    if not platform.startswith('win'):
-                        self.announce(
-                            '\tNSIS installer not supported for platform: {}'.format(platform),
-                            distutils.log.ERROR
-                        )
-                        continue
-                    try:
-                        subprocess.call(['makensis', '--version'])
-                    except OSError:
-                        self.announce(
-                            '\tCould not find makensis tool that is required to build NSIS installers',
-                            distutils.log.ERROR
-                        )
-                        # continue
-                    is_64bit = platform == 'win_amd64'
-                    self.create_nsis(basename, build_dir, is_64bit)
-
-                else:
-                    self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR)
+                self.installer_functions[installer](self, basename, build_dir)

+ 173 - 0
direct/src/dist/installers.py

@@ -0,0 +1,173 @@
+import distutils.log
+import os
+import subprocess
+import sys
+import tarfile
+import zipfile
+
+import panda3d.core as p3d
+
+def create_zip(command, basename, build_dir):
+    base_dir = command.get_archive_basedir()
+
+    with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
+        zf.write(build_dir, base_dir)
+
+        for dirpath, dirnames, filenames in os.walk(build_dir):
+            for name in sorted(dirnames):
+                path = os.path.normpath(os.path.join(dirpath, name))
+                zf.write(path, path.replace(build_dir, base_dir, 1))
+            for name in filenames:
+                path = os.path.normpath(os.path.join(dirpath, name))
+                if os.path.isfile(path):
+                    zf.write(path, path.replace(build_dir, base_dir, 1))
+
+
+def create_tarball(command, basename, build_dir, tar_compression):
+    base_dir = command.get_archive_basedir()
+    build_cmd = command.get_finalized_command('build_apps')
+    binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
+
+    def tarfilter(tarinfo):
+        if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
+            tarinfo.mode = 0o755
+        else:
+            tarinfo.mode = 0o644
+        return tarinfo
+
+    with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
+        tf.add(build_dir, base_dir, filter=tarfilter)
+
+
+def create_gztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'gz')
+
+
+def create_bztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'bz2')
+
+
+def create_xztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'xz')
+
+
+def create_nsis(command, basename, build_dir):
+    platform = command.get_current_platform()
+    if not platform.startswith('win'):
+        command.announce(
+            '\tNSIS installer not supported for platform: {}'.format(platform),
+            distutils.log.ERROR
+        )
+        return
+    try:
+        subprocess.call(['makensis', '--version'])
+    except OSError:
+        command.announce(
+            '\tCould not find makensis tool that is required to build NSIS installers',
+            distutils.log.ERROR
+        )
+        return
+
+    is_64bit = platform == 'win_amd64'
+    # Get a list of build applications
+    build_cmd = command.get_finalized_command('build_apps')
+    apps = build_cmd.gui_apps.copy()
+    apps.update(build_cmd.console_apps)
+    apps = [
+        '{}.exe'.format(i)
+        for i in apps
+    ]
+
+    shortname = command.distribution.get_name()
+
+    # Create the .nsi installer script
+    nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
+    nsifile.unlink()
+    nsi = open(nsifile.to_os_specific(), "w")
+
+    # Some global info
+    nsi.write('Name "%s"\n' % shortname)
+    nsi.write('OutFile "%s"\n' % os.path.join(command.dist_dir, basename+'.exe'))
+    if is_64bit:
+        nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
+    else:
+        nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
+    nsi.write('SetCompress auto\n')
+    nsi.write('SetCompressor lzma\n')
+    nsi.write('ShowInstDetails nevershow\n')
+    nsi.write('ShowUninstDetails nevershow\n')
+    nsi.write('InstType "Typical"\n')
+
+    # Tell Vista that we require admin rights
+    nsi.write('RequestExecutionLevel admin\n')
+    nsi.write('\n')
+
+    # TODO offer run and desktop shortcut after we figure out how to deal
+    # with multiple apps
+
+    nsi.write('!include "MUI2.nsh"\n')
+    nsi.write('!define MUI_ABORTWARNING\n')
+    nsi.write('\n')
+    nsi.write('Var StartMenuFolder\n')
+    nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
+    # TODO license file
+    nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
+    nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
+    nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
+    nsi.write('!insertmacro MUI_PAGE_FINISH\n')
+    nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
+    nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
+    nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
+    nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
+    nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
+
+    # This section defines the installer.
+    nsi.write('Section "" SecCore\n')
+    nsi.write('  SetOutPath "$INSTDIR"\n')
+    curdir = ""
+    nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
+    build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
+    for root, dirs, files in os.walk(build_dir):
+        for name in files:
+            basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
+            file = p3d.Filename(basefile)
+            file.makeAbsolute()
+            file.makeRelativeTo(nsi_dir)
+            outdir = p3d.Filename(basefile)
+            outdir.makeAbsolute()
+            outdir.makeRelativeTo(build_root_dir)
+            outdir = outdir.getDirname().replace('/', '\\')
+            if curdir != outdir:
+                nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
+                curdir = outdir
+            nsi.write('  File "%s"\n' % (file.toOsSpecific()))
+    nsi.write('  SetOutPath "$INSTDIR"\n')
+    nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
+    nsi.write('  ; Start menu items\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
+    nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
+    for app in apps:
+        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
+    nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
+    nsi.write('SectionEnd\n')
+
+    # This section defines the uninstaller.
+    nsi.write('Section Uninstall\n')
+    nsi.write('  RMDir /r "$INSTDIR"\n')
+    nsi.write('  ; Desktop icon\n')
+    nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
+    nsi.write('  ; Start menu items\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
+    nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
+    nsi.write('SectionEnd\n')
+    nsi.close()
+
+    cmd = ['makensis']
+    for flag in ["V2"]:
+        cmd.append(
+            '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
+        )
+    cmd.append(nsifile.to_os_specific())
+    subprocess.check_call(cmd)
+

+ 1 - 4
direct/src/dist/pefile.py

@@ -242,10 +242,7 @@ class VersionInfoResource(object):
         length, value_length = unpack('<HH', data[0:4])
         offset = 40 + value_length + (value_length & 1)
         dwords = array('I')
-        if sys.version_info >= (3, 2):
-            dwords.frombytes(bytes(data[40:offset]))
-        else:
-            dwords.fromstring(bytes(data[40:offset]))
+        dwords.frombytes(bytes(data[40:offset]))
 
         if len(dwords) > 0:
             self.signature = dwords[0]

+ 2 - 2
direct/src/distributed/cConnectionRepository.cxx

@@ -918,8 +918,8 @@ describe_message(std::ostream &out, const string &prefix,
       PyObject *methodName = PyUnicode_FromString("_getMsgName");
       nassertv(methodName != nullptr);
 
-      PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
-                                                    msgId, nullptr);
+      PyObject *result =
+        PyObject_CallMethodOneArg(_python_repository, methodName, msgId);
       nassertv(result != nullptr);
 
       msgName += string(PyUnicode_AsUTF8(result));

+ 3 - 2
direct/src/extensions_native/NodePath_extensions.py

@@ -432,8 +432,8 @@ def rgbPanel(self, cb = None):
         # Don't use a regular import, to prevent ModuleFinder from picking
         # it up as a dependency when building a .p3d package.
         import importlib
-        Slider = importlib.import_module('direct.tkwidgets.Slider')
-        return Slider.rgbPanel(self, cb)
+        Valuator = importlib.import_module('direct.tkwidgets.Valuator')
+        return Valuator.rgbPanel(self, cb)
 
 Dtool_funcToMethod(rgbPanel, NodePath)
 del rgbPanel
@@ -789,6 +789,7 @@ def r_subdivideCollisions(self, solids, numSolidsInLeaves):
         return newSolids
 
 def r_constructCollisionTree(self, solidTree, parentNode, colName):
+        from panda3d.core import CollisionNode
         for item in solidTree:
             if type(item[0]) == type([]):
                 newNode = parentNode.attachNewNode('%s-branch' % colName)

+ 3 - 2
direct/src/gui/OnscreenGeom.py

@@ -42,8 +42,9 @@ class OnscreenGeom(DirectObject, NodePath):
         """
         # We ARE a node path.  Initially, we're an empty node path.
         NodePath.__init__(self)
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
 
         self.setGeom(geom, parent = parent, sort = sort, color = color)
 

+ 3 - 2
direct/src/gui/OnscreenImage.py

@@ -48,8 +48,9 @@ class OnscreenImage(DirectObject, NodePath):
         # We ARE a node path.  Initially, we're an empty node path.
         NodePath.__init__(self)
 
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
         self.setImage(image, parent = parent, sort = sort)
 
         # Adjust pose

+ 113 - 12
direct/src/gui/OnscreenText.py

@@ -103,8 +103,9 @@ class OnscreenText(NodePath):
           direction: this can be set to 'ltr' or 'rtl' to override the
               direction of the text.
         """
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
 
         # make a text node
         textNode = TextNode('')
@@ -292,43 +293,139 @@ class OnscreenText(NodePath):
 
     text = property(getText, setText)
 
+    def setTextX(self, x):
+        """
+        .. versionadded:: 1.10.8
+        """
+        self.setTextPos(x, self.__pos[1])
+
     def setX(self, x):
-        self.setPos(x, self.__pos[1])
+        """
+        .. deprecated:: 1.11.0
+           Use `.setTextX()` method instead.
+        """
+        self.setTextPos(x, self.__pos[1])
+
+    def setTextY(self, y):
+        """
+        .. versionadded:: 1.10.8
+        """
+        self.setTextPos(self.__pos[0], y)
 
     def setY(self, y):
-        self.setPos(self.__pos[0], y)
+        """
+        .. deprecated:: 1.11.0
+           Use `.setTextY()` method instead.
+        """
+        self.setTextPos(self.__pos[0], y)
+
+    def setTextPos(self, x, y=None):
+        """
+        Position the onscreen text in 2d screen space
+
+        .. versionadded:: 1.10.8
+        """
+        if y is None:
+            self.__pos = tuple(x)
+        else:
+            self.__pos = (x, y)
+        self.updateTransformMat()
+
+    def getTextPos(self):
+        """
+        .. versionadded:: 1.10.8
+        """
+        return self.__pos
+
+    text_pos = property(getTextPos, setTextPos)
 
     def setPos(self, x, y):
         """setPos(self, float, float)
         Position the onscreen text in 2d screen space
+
+        .. deprecated:: 1.11.0
+           Use `.setTextPos()` method or `.text_pos` property instead.
         """
         self.__pos = (x, y)
         self.updateTransformMat()
 
     def getPos(self):
+        """
+        .. deprecated:: 1.11.0
+           Use `.getTextPos()` method or `.text_pos` property instead.
+        """
         return self.__pos
 
-    pos = property(getPos, setPos)
+    pos = property(getPos)
+
+    def setTextR(self, r):
+        """setTextR(self, float)
+        Rotates the text around the screen's normal.
+
+        .. versionadded:: 1.10.8
+        """
+        self.__roll = -r
+        self.updateTransformMat()
+
+    def getTextR(self):
+        return -self.__roll
+
+    text_r = property(getTextR, setTextR)
 
     def setRoll(self, roll):
         """setRoll(self, float)
-        Rotate the onscreen text around the screen's normal
+        Rotate the onscreen text around the screen's normal.
+
+        .. deprecated:: 1.11.0
+           Use ``setTextR(-roll)`` instead (note the negated sign).
         """
         self.__roll = roll
         self.updateTransformMat()
 
     def getRoll(self):
+        """
+        .. deprecated:: 1.11.0
+           Use ``-getTextR()`` instead (note the negated sign).
+        """
         return self.__roll
 
     roll = property(getRoll, setRoll)
 
+    def setTextScale(self, sx, sy = None):
+        """setTextScale(self, float, float)
+        Scale the text in 2d space.  You may specify either a single
+        uniform scale, or two scales, or a tuple of two scales.
+
+        .. versionadded:: 1.10.8
+        """
+
+        if sy is None:
+            if isinstance(sx, tuple):
+                self.__scale = sx
+            else:
+                self.__scale = (sx, sx)
+        else:
+            self.__scale = (sx, sy)
+        self.updateTransformMat()
+
+    def getTextScale(self):
+        """
+        .. versionadded:: 1.10.8
+        """
+        return self.__scale
+
+    text_scale = property(getTextScale, setTextScale)
+
     def setScale(self, sx, sy = None):
         """setScale(self, float, float)
         Scale the text in 2d space.  You may specify either a single
         uniform scale, or two scales, or a tuple of two scales.
+
+        .. deprecated:: 1.11.0
+           Use `.setTextScale()` method or `.text_scale` property instead.
         """
 
-        if sy == None:
+        if sy is None:
             if isinstance(sx, tuple):
                 self.__scale = sx
             else:
@@ -337,6 +434,15 @@ class OnscreenText(NodePath):
             self.__scale = (sx, sy)
         self.updateTransformMat()
 
+    def getScale(self):
+        """
+        .. deprecated:: 1.11.0
+           Use `.getTextScale()` method or `.text_scale` property instead.
+        """
+        return self.__scale
+
+    scale = property(getScale, setScale)
+
     def updateTransformMat(self):
         assert(isinstance(self.textNode, TextNode))
         mat = (
@@ -346,11 +452,6 @@ class OnscreenText(NodePath):
             )
         self.textNode.setTransform(mat)
 
-    def getScale(self):
-        return self.__scale
-
-    scale = property(getScale, setScale)
-
     def setWordwrap(self, wordwrap):
         self.__wordwrap = wordwrap
 

+ 0 - 38
direct/src/showbase/showBase.cxx

@@ -91,44 +91,6 @@ init_app_for_gui() {
   */
 }
 
-// klunky interface since we cant pass array from python->C++ to use
-// verify_window_sizes directly
-static int num_fullscreen_testsizes = 0;
-#define MAX_FULLSCREEN_TESTS 10
-static int fullscreen_testsizes[MAX_FULLSCREEN_TESTS * 2];
-
-void
-add_fullscreen_testsize(int xsize, int ysize) {
-  if ((xsize == 0) && (ysize == 0)) {
-    num_fullscreen_testsizes = 0;
-    return;
-  }
-
-  // silently fail if maxtests exceeded
-  if (num_fullscreen_testsizes < MAX_FULLSCREEN_TESTS) {
-    fullscreen_testsizes[num_fullscreen_testsizes * 2] = xsize;
-    fullscreen_testsizes[num_fullscreen_testsizes * 2 + 1] = ysize;
-    num_fullscreen_testsizes++;
-  }
-}
-
-void
-runtest_fullscreen_sizes(GraphicsWindow *win) {
-  win->verify_window_sizes(num_fullscreen_testsizes, fullscreen_testsizes);
-}
-
-bool
-query_fullscreen_testresult(int xsize, int ysize) {
-  // stupid linear search that works ok as long as total tests are small
-  int i;
-  for (i=0; i < num_fullscreen_testsizes; i++) {
-    if((fullscreen_testsizes[i * 2] == xsize) &&
-       (fullscreen_testsizes[i * 2 + 1] == ysize))
-      return true;
-  }
-  return false;
-}
-
 void
 store_accessibility_shortcut_keys() {
 #ifdef _WIN32

+ 0 - 5
direct/src/showbase/showBase.h

@@ -40,11 +40,6 @@ EXPCL_DIRECT_SHOWBASE void throw_new_frame();
 
 EXPCL_DIRECT_SHOWBASE void init_app_for_gui();
 
-// klunky interface since we cant pass array from python->C++
-EXPCL_DIRECT_SHOWBASE void add_fullscreen_testsize(int xsize, int ysize);
-EXPCL_DIRECT_SHOWBASE void runtest_fullscreen_sizes(GraphicsWindow *win);
-EXPCL_DIRECT_SHOWBASE bool query_fullscreen_testresult(int xsize, int ysize);
-
 // to handle windows stickykeys
 EXPCL_DIRECT_SHOWBASE void store_accessibility_shortcut_keys();
 EXPCL_DIRECT_SHOWBASE void allow_accessibility_shortcut_keys(bool allowKeys);

+ 37 - 11
direct/src/stdpy/pickle.py

@@ -30,19 +30,26 @@ from copyreg import dispatch_table
 # with the local pickle.py.
 pickle = __import__('pickle')
 
-class Pickler(pickle.Pickler):
+BasePickler = pickle._Pickler
+BaseUnpickler = pickle._Unpickler
+
+
+class _Pickler(BasePickler):
 
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
-        pickle.Pickler.__init__(self, *args, **kw)
+        BasePickler.__init__(self, *args, **kw)
 
     # We have to duplicate most of the save() method, so we can add
     # support for __reduce_persist__().
 
-    def save(self, obj):
+    def save(self, obj, save_persistent_id=True):
+        if self.proto >= 4:
+            self.framer.commit_frame()
+
         # Check for persistent id (defined by a subclass)
         pid = self.persistent_id(obj)
-        if pid:
+        if pid is not None and save_persistent_id:
             self.save_pers(pid)
             return
 
@@ -109,11 +116,12 @@ class Pickler(pickle.Pickler):
         # Save the reduce() output and finally memoize the object
         self.save_reduce(obj=obj, *rv)
 
-class Unpickler(pickle.Unpickler):
+
+class Unpickler(BaseUnpickler):
 
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()
-        pickle.Unpickler.__init__(self, *args, **kw)
+        BaseUnpickler.__init__(self, *args, **kw)
 
     # Duplicate the load_reduce() function, to provide a special case
     # for the reduction function.
@@ -123,9 +131,10 @@ class Unpickler(pickle.Unpickler):
         args = stack.pop()
         func = stack[-1]
 
-        # If the function name ends with "Persist", then assume the
+        # If the function name ends with "_persist", then assume the
         # function wants the Unpickler as the first parameter.
-        if func.__name__.endswith('Persist'):
+        func_name = func.__name__
+        if func_name.endswith('_persist') or func_name.endswith('Persist'):
             value = func(self, *args)
         else:
             # Otherwise, use the existing pickle convention.
@@ -133,9 +142,26 @@ class Unpickler(pickle.Unpickler):
 
         stack[-1] = value
 
-    #FIXME: how to replace in Python 3?
-    if sys.version_info < (3, 0):
-        pickle.Unpickler.dispatch[pickle.REDUCE] = load_reduce
+    BaseUnpickler.dispatch[pickle.REDUCE[0]] = load_reduce
+
+
+if sys.version_info >= (3, 8):
+    # In Python 3.8 and up, we can use the C implementation of Pickler, which
+    # supports a reducer_override method.
+    class Pickler(pickle.Pickler):
+        def __init__(self, *args, **kw):
+            self.bamWriter = BamWriter()
+            pickle.Pickler.__init__(self, *args, **kw)
+
+        def reducer_override(self, obj):
+            reduce = getattr(obj, "__reduce_persist__", None)
+            if reduce:
+                return reduce(self)
+
+            return NotImplemented
+else:
+    # Otherwise, we have to use our custom version that overrides save().
+    Pickler = _Pickler
 
 
 # Shorthands

+ 37 - 15
direct/src/tkwidgets/EntryScale.py

@@ -5,6 +5,7 @@ EntryScale Class: Scale with a label, and a linked and validated entry
 __all__ = ['EntryScale', 'EntryScaleGroup']
 
 from direct.showbase.TkGlobal import *
+from panda3d.core import Vec4
 import Pmw
 from tkinter.simpledialog import *
 from tkinter.colorchooser import askcolor
@@ -477,17 +478,24 @@ def rgbPanel(nodePath, callback = None):
     esg.component('menubar').component('EntryScale Group-button')['text'] = (
         'RGBA Panel')
     # Update menu
-    menu = esg.component('menubar').component('EntryScale Group-menu')
+    menubar = esg.component('menubar')
+    menubar.deletemenuitems('EntryScale Group', 1, 1)
+
     # Some helper functions
     # Clear color
-    menu.insert_command(index = 1, label = 'Clear Color',
-                        command = lambda np = nodePath: np.clearColor())
-    # Set Clear Transparency
-    menu.insert_command(index = 2, label = 'Set Transparency',
-                        command = lambda np = nodePath: np.setTransparency(1))
-    menu.insert_command(
-        index = 3, label = 'Clear Transparency',
-        command = lambda np = nodePath: np.clearTransparency())
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Clear Color', command=lambda np=nodePath: np.clearColor())
+
+    # Set/Clear Transparency
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Set Transparency', command=lambda np=nodePath: np.setTransparency(1))
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Clear Transparency',
+        command=lambda np=nodePath: np.clearTransparency())
+
     # System color picker
     def popupColorPicker(esg = esg):
         # Can pass in current color with: color = (255, 0, 0)
@@ -497,13 +505,27 @@ def rgbPanel(nodePath, callback = None):
             initialcolor = tuple(esg.get()[:3]))[0]
         if color:
             esg.set((color[0], color[1], color[2], esg.getAt(3)))
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
+
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+
     def printToLog(nodePath=nodePath):
-        c=nodePath.getColor()
-        print("Vec4(%.3f, %.3f, %.3f, %.3f)"%(c[0], c[1], c[2], c[3]))
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+        c = nodePath.getColor()
+        print("Vec4(%.3f, %.3f, %.3f, %.3f)" % (c[0], c[1], c[2], c[3]))
+
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Print to log', command=printToLog)
+
+    # Add back the Dismiss item we removed.
+    if esg['fDestroy']:
+        dismissCommand = esg.destroy
+    else:
+        dismissCommand = esg.withdraw
+    menubar.addmenuitem(
+        'EntryScale Group', 'command', 'Dismiss EntryScale Group panel',
+        label='Dismiss', command=dismissCommand)
 
     # Set callback
     def onRelease(r, g, b, a, nodePath = nodePath):

+ 28 - 0
doc/ReleaseNotes

@@ -1,3 +1,31 @@
+------------------------  RELEASE 1.10.8  -----------------------
+
+Recommended maintenance release.
+
+* Support building for macOS 11 "Big Sur" and "Apple Silicon" (arm64)
+* Fix a memory leak, particularly noticeable with multithreaded pipeline (#1077)
+* Fix crash on macOS when unplugging device with threading active (#1082)
+* Fix error with build_apps not working with certain versions of Python
+* Fix DirectEntry/PGEntry flickering in the multithreaded pipeline (#1070)
+* Fix sounds resuming on reactivation if stop() was called while inactive (#559)
+* Collision traverser now releases GIL during traversal (#1033)
+* Fix crash caused by some gamepad drivers on Linux (#1066)
+* Add GraphicsPipe::get_display_zoom() for querying system DPI scaling
+* Skinning-enabled shaders can now properly render unskinned models as well
+* BitMask, SparseArray, BitArray types can now be pickled (#886)
+* VFSImporter now properly detects source file encodings in Python 3
+* Workaround for lighting bug with scenes imported using panda3d-gltf/blend2bam
+* Fix compilation error with Bullet 2.90+
+* Assimp library was updated in Windows thirdparty packages (#1020)
+* libCg is now shipped as library instead of framework on macOS (#1079)
+* Fix some erroneous warnings about missing modules in build_apps
+* Add warnings to build_apps when forgetting dependencies in requirements.txt
+* Add experimental TextureStage::M_emission mode
+* Add experimental p3d_TextureNormal, p3d_TextureEmission, etc. GLSL inputs
+* Fix ability to use deployment system when compiling without OpenSSL (#1073)
+* Fix assorted issues with rgbPanel
+* Fix comparison operator of RenderEffects object
+
 ------------------------  RELEASE 1.10.7  -----------------------
 
 This is primarily a bugfix release, but includes a few new features as well.

+ 0 - 4
dtool/Config.cmake

@@ -29,10 +29,6 @@ if(DEFINED CMAKE_CXX_FLAGS_COVERAGE)
   list(APPEND _configs Coverage)
 endif()
 
-if(IS_MULTICONFIG)
-  set(CMAKE_CONFIGURATION_TYPES ${_configs})
-endif()
-
 # Are we building with static or dynamic linking?
 option(BUILD_SHARED_LIBS
   "Causes subpackages to be built separately -- setup for dynamic linking.

+ 16 - 3
dtool/LocalSetup.cmake

@@ -6,6 +6,7 @@
 # file based on the user's selected configure variables.
 #
 
+include(CheckCXXCompilerFlag)
 include(CheckCXXSourceCompiles)
 include(CheckCSourceRuns)
 include(CheckIncludeFileCXX)
@@ -137,8 +138,18 @@ check_include_file_cxx(stdint.h PHAVE_STDINT_H)
 #set(HAVE_POSIX_THREADS ${CMAKE_USE_PTHREADS_INIT})
 
 # Do we have SSE2 support?
-include(CheckCXXCompilerFlag)
-check_cxx_compiler_flag(-msse2 HAVE_SSE2)
+if(MSVC)
+  check_cxx_source_compiles("
+#if !defined(__SSE2__) && !defined(_M_X64) && !defined(_M_AMD64) && !defined(_M_IX86_FP)
+#error no
+#endif
+int main (int argc, char *argv[]) {
+  return 0;
+}
+" HAVE_SSE2)
+else()
+  check_cxx_compiler_flag(-msse2 HAVE_SSE2)
+endif()
 
 # Set LINK_ALL_STATIC if we're building everything as static libraries.
 # Also set the library type used for "modules" appropriately.
@@ -158,7 +169,9 @@ endif()
 show_packages()
 
 message("")
-if(INTERROGATE_PYTHON_INTERFACE)
+if(HAVE_PYTHON AND NOT PYTHON_FOUND)
+  message(SEND_ERROR "Configured Panda with Python bindings, but no Python library found.  Disable HAVE_PYTHON to continue.")
+elseif(INTERROGATE_PYTHON_INTERFACE)
   message("Compilation will generate Python interfaces for Python ${PYTHON_VERSION_STRING}.")
 else()
   message("Configuring Panda WITHOUT Python interfaces.")

+ 2 - 0
dtool/dtool_config.h.in

@@ -63,7 +63,9 @@
 #cmakedefine HAVE_PNM
 
 /* Define if we have CG installed.  */
+#ifndef __aarch64__
 #cmakedefine HAVE_CG
+#endif
 
 /* Define if we have zlib installed.  */
 #cmakedefine HAVE_ZLIB

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

@@ -57,7 +57,7 @@ private:
     size_t _remaining;
   };
 
-  typedef std::set<Page> Pages;
+  typedef std::multiset<Page> Pages;
   Pages _pages;
 
   size_t _total_alloc;

+ 2 - 0
dtool/src/dtoolutil/CMakeLists.txt

@@ -82,6 +82,8 @@ if(APPLE)
   find_library(FOUNDATION_LIBRARY Foundation)
   find_library(APPKIT_LIBRARY AppKit)
   target_link_libraries(p3dtoolutil ${FOUNDATION_LIBRARY} ${APPKIT_LIBRARY})
+
+  mark_as_advanced(FOUNDATION_LIBRARY APPKIT_LIBRARY)
 endif()
 
 # These are all used by executionEnvironment.cxx/filename.cxx

+ 7 - 3
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -255,10 +255,14 @@ get_cwd() {
  */
 bool ExecutionEnvironment::
 ns_has_environment_variable(const string &var) const {
-#ifdef PREREAD_ENVIRONMENT
-  return _variables.count(var) != 0;
-#else
+  if (_variables.count(var) != 0) {
+    return true;
+  }
+
+#ifndef PREREAD_ENVIRONMENT
   return getenv(var.c_str()) != nullptr;
+#else
+  return false;
 #endif
 }
 

+ 9 - 0
dtool/src/dtoolutil/panda_getopt_impl.cxx

@@ -491,4 +491,13 @@ getopt_long_only(int argc, char *const argv[], const char *optstring,
   return pgetopt->process(opterr, longindex, optarg, optind, optopt);
 }
 
+/**
+ * Resets the internal PandaGetopt state.
+ * This is a necessary step to reset getopt state in general.
+ */
+void
+pgetopt_reset() {
+  pgetopt = nullptr;
+}
+
 #endif  // defined(HAVE_GETOPT) && defined(HAVE_GETOPT_LONG_ONLY)

+ 2 - 0
dtool/src/dtoolutil/panda_getopt_impl.h

@@ -62,6 +62,8 @@ getopt_long(int argc, char *const argv[], const char *optstring,
 extern EXPCL_DTOOL_DTOOLUTIL int
 getopt_long_only(int argc, char *const argv[], const char *optstring,
                  const struct option *longopts, int *longindex);
+extern EXPCL_DTOOL_DTOOLUTIL void
+pgetopt_reset();
 
 #ifdef  __cplusplus
 }

+ 18 - 0
dtool/src/interrogatedb/py_compat.cxx

@@ -43,4 +43,22 @@ size_t PyLongOrInt_AsSize_t(PyObject *vv) {
 }
 #endif
 
+#if PY_VERSION_HEX < 0x03090000
+/**
+ * Most efficient way to call a function without any arguments.
+ */
+PyObject *PyObject_CallNoArgs(PyObject *func) {
+#if PY_VERSION_HEX >= 0x03080000
+  return _PyObject_Vectorcall(func, nullptr, 0, nullptr);
+#elif PY_VERSION_HEX >= 0x03070000
+  return _PyObject_FastCallDict(func, nullptr, 0, nullptr);
+#elif PY_VERSION_HEX >= 0x03060000
+  return _PyObject_FastCall(func, nullptr, 0);
+#else
+  static PyObject *empty_tuple = PyTuple_New(0);
+  return PyObject_Call(func, empty_tuple, nullptr);
+#endif
+}
+#endif
+
 #endif  // HAVE_PYTHON

+ 23 - 5
dtool/src/interrogatedb/py_compat.h

@@ -140,11 +140,7 @@ typedef long Py_hash_t;
 /* Python 3.6 */
 
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_CallNoArg)
-INLINE PyObject *_PyObject_CallNoArg(PyObject *func) {
-  static PyObject *empty_tuple = PyTuple_New(0);
-  return PyObject_Call(func, empty_tuple, nullptr);
-}
-#  define _PyObject_CallNoArg _PyObject_CallNoArg
+#  define _PyObject_CallNoArg PyObject_CallNoArgs
 #endif
 
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_FastCall)
@@ -208,6 +204,28 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) {
 }
 #endif
 
+/* Python 3.9 */
+
+#if PY_VERSION_HEX < 0x03090000
+EXPCL_PYPANDA PyObject *PyObject_CallNoArgs(PyObject *func);
+
+INLINE PyObject *PyObject_CallOneArg(PyObject *callable, PyObject *arg) {
+#if PY_VERSION_HEX >= 0x03060000
+  return _PyObject_FastCall(callable, &arg, 1);
+#else
+  return PyObject_CallFunctionObjArgs(callable, arg, nullptr);
+#endif
+}
+
+INLINE PyObject *PyObject_CallMethodNoArgs(PyObject *obj, PyObject *name) {
+  return PyObject_CallMethodObjArgs(obj, name, nullptr);
+}
+
+INLINE PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObject *arg) {
+  return PyObject_CallMethodObjArgs(obj, name, arg, nullptr);
+}
+#endif
+
 /* Other Python implementations */
 
 // _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.

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

@@ -755,7 +755,7 @@ PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) {
  */
 PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) {
   PyObject *callable = (PyObject *)Py_TYPE(self);
-  return _PyObject_FastCall(callable, &self, 1);
+  return PyObject_CallOneArg(callable, self);
 }
 
 /**

+ 1 - 1
dtool/src/interrogatedb/py_wrappers.cxx

@@ -30,7 +30,7 @@ static void _register_collection(PyTypeObject *type, const char *abc) {
 #endif
         PyObject *sequence = PyDict_GetItemString(dict, abc);
         if (sequence != nullptr) {
-          if (PyObject_CallMethodObjArgs(sequence, register_str, (PyObject *)type, nullptr) == nullptr) {
+          if (PyObject_CallMethodOneArg(sequence, register_str, (PyObject *)type) == nullptr) {
             PyErr_Print();
           }
         }

+ 30 - 10
makepanda/installer.nsi

@@ -30,6 +30,7 @@ SetCompressor ${COMPRESSOR}
 !include "MUI2.nsh"
 !include "Sections.nsh"
 !include "WinMessages.nsh"
+!include "WinVer.nsh"
 !include "WordFunc.nsh"
 !include "x64.nsh"
 
@@ -89,7 +90,6 @@ LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${
 LangString DESC_SecMayaPlugins ${LANG_ENGLISH} "Plug-ins and scripts for Autodesk Maya (${REGVIEW}-bit) that can be used to export models to Panda3D."
 
 var READABLE
-var MANPAGE
 
 ; See http://nsis.sourceforge.net/Check_if_a_file_exists_at_compile_time for documentation
 !macro !defineifexist _VAR_NAME _FILE_NAME
@@ -373,12 +373,14 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd
         !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
+        !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
         !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd
         !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
+        !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
     !endif
 SectionGroupEnd
 
@@ -483,14 +485,32 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.6-32
         !insertmacro MaybeEnablePyBindingSection 3.7-32
         !insertmacro MaybeEnablePyBindingSection 3.8-32
+        ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9-32
+        !insertmacro MaybeEnablePyBindingSection 3.10-32
+        ${EndIf}
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
         !insertmacro MaybeEnablePyBindingSection 3.6
         !insertmacro MaybeEnablePyBindingSection 3.7
         !insertmacro MaybeEnablePyBindingSection 3.8
+        ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9
+        !insertmacro MaybeEnablePyBindingSection 3.10
+        ${EndIf}
+    !endif
+
+    ; These versions of Python require Windows 8.1 or higher.
+    ${Unless} ${AtLeastWin8}
+    !ifdef SecPyBindings3.9
+        SectionSetFlags ${SecPyBindings3.9} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.9} 0
+    !endif
+    !ifdef SecPyBindings3.10
+        SectionSetFlags ${SecPyBindings3.10} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.10} 0
     !endif
+    ${EndUnless}
 FunctionEnd
 
 Function .onSelChange
@@ -599,13 +619,14 @@ Section "Sample programs" SecSamples
 
     SetOutPath $INSTDIR
     WriteINIStr $INSTDIR\Website.url "InternetShortcut" "URL" "https://www.panda3d.org/"
-    WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php"
-    WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php/Sample_Programs_in_the_Distribution"
+    WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}"
+    WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/index"
     SetOutPath $INSTDIR
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Website"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Sample Program Manual"
 
+    ${Unless} ${AtLeastWin8}
     FindFirst $0 $1 $INSTDIR\samples\*
     loop:
         StrCmp $1 "" done
@@ -620,16 +641,10 @@ Section "Sample programs" SecSamples
         Call Capitalize
         Pop $R0
         StrCpy $READABLE $R0
-        Push $1
-        Push "-"
-        Push "_"
-        Call StrRep
-        Pop $R0
-        StrCpy $MANPAGE $R0
         DetailPrint "Creating shortcuts for sample program $READABLE"
         CreateDirectory "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE"
         SetOutPath $INSTDIR\samples\$1
-        WriteINIStr $INSTDIR\samples\$1\ManualPage.url "InternetShortcut" "URL" "https://www.panda3d.org/wiki/index.php/Sample_Programs:_$MANPAGE"
+        WriteINIStr $INSTDIR\samples\$1\ManualPage.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/$1"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\Manual Page.lnk" "$INSTDIR\samples\$1\ManualPage.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Manual Entry on this Sample Program"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\View Source Code.lnk" "$INSTDIR\samples\$1"
         iloop:
@@ -643,6 +658,7 @@ Section "Sample programs" SecSamples
         FindNext $0 $1
         Goto loop
     done:
+    ${EndUnless}
 SectionEnd
 !endif
 
@@ -803,12 +819,14 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.7-32
         !insertmacro RemovePythonPath 3.8-32
         !insertmacro RemovePythonPath 3.9-32
+        !insertmacro RemovePythonPath 3.10-32
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
         !insertmacro RemovePythonPath 3.7
         !insertmacro RemovePythonPath 3.8
         !insertmacro RemovePythonPath 3.9
+        !insertmacro RemovePythonPath 3.10
     !endif
 
     SetDetailsPrint both
@@ -876,12 +894,14 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
   !endif
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 3 - 1
makepanda/makepackage.py

@@ -153,6 +153,7 @@ def MakeInstallerNSIS(version, file, title, installdir, compressor="lzma", **kwa
         'BUILT'     : '..\\' + outputdir,
         'SOURCE'    : '..',
         'REGVIEW'   : regview,
+        'MAJOR_VER' : '.'.join(version.split('.')[:2]),
     }
 
     # Are we shipping a version of Python?
@@ -388,7 +389,8 @@ def MakeInstallerOSX(version, python_versions=[], installdir=None, **kwargs):
     oscmd("cp -R %s/models                dstroot/base/%s/models" % (outputdir, installdir))
     oscmd("cp -R doc/LICENSE              dstroot/base/%s/LICENSE" % installdir)
     oscmd("cp -R doc/ReleaseNotes         dstroot/base/%s/ReleaseNotes" % installdir)
-    oscmd("cp -R %s/Frameworks            dstroot/base/%s/Frameworks" % (outputdir, installdir))
+    if os.path.isdir(outputdir+"/Frameworks") and os.listdir(outputdir+"/Frameworks"):
+        oscmd("cp -R %s/Frameworks            dstroot/base/%s/Frameworks" % (outputdir, installdir))
     if os.path.isdir(outputdir+"/plugins"):
         oscmd("cp -R %s/plugins           dstroot/base/%s/plugins" % (outputdir, installdir))
 

+ 181 - 96
makepanda/makepanda.py

@@ -30,6 +30,11 @@ import time
 import os
 import sys
 
+try:
+    import zlib
+except:
+    zlib = None
+
 ########################################################################
 ##
 ## PARSING THE COMMAND LINE OPTIONS
@@ -58,7 +63,7 @@ WHLVERSION=None
 RPMRELEASE="1"
 GIT_COMMIT=None
 MAJOR_VERSION=None
-OSXTARGET=None
+OSX_ARCHS=[]
 global STRDXSDKVERSION, BOOUSEINTELCOMPILER
 STRDXSDKVERSION = 'default'
 WINDOWS_SDK = None
@@ -68,9 +73,6 @@ OPENCV_VER_23 = False
 PLATFORM = None
 COPY_PYTHON = True
 
-if "MACOSX_DEPLOYMENT_TARGET" in os.environ:
-    OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"]
-
 PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "GL", "GLES", "GLES2"] + DXVERSIONS + ["TINYDISPLAY", "NVIDIACG", # 3D graphics
   "EGL",                                               # OpenGL (ES) integration
@@ -132,7 +134,7 @@ def usage(problem):
     print("  --distributor X   (short string identifying the distributor of the build)")
     print("  --outputdir X     (use the specified directory instead of 'built')")
     print("  --threads N       (use the multithreaded build system. see manual)")
-    print("  --osxtarget N     (the macOS version number to build for (macOS only))")
+    print("  --universal       (build universal binaries (macOS 11.0+ only))")
     print("  --override \"O=V\"  (override dtool_config/prc option value)")
     print("  --static          (builds libraries for static linking)")
     print("  --target X        (experimental cross-compilation (android only))")
@@ -160,7 +162,7 @@ def usage(problem):
 
 def parseopts(args):
     global INSTALLER,WHEEL,RUNTESTS,GENMAN,DISTRIBUTOR,VERSION
-    global COMPRESSOR,THREADCOUNT,OSXTARGET
+    global COMPRESSOR,THREADCOUNT,OSX_ARCHS
     global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
     global COPY_PYTHON
@@ -168,24 +170,25 @@ def parseopts(args):
     # Options for which to display a deprecation warning.
     removedopts = [
         "use-touchinput", "no-touchinput", "no-awesomium", "no-directscripts",
-        "no-carbon", "universal", "no-physx", "no-rocket", "host"
+        "no-carbon", "no-physx", "no-rocket", "host", "osxtarget=",
         ]
 
     # All recognized options.
     longopts = [
-        "help","distributor=","verbose","osxtarget=","tests",
+        "help","distributor=","verbose","tests",
         "optimize=","everything","nothing","installer","wheel","rtdist","nocolor",
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "static","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
-        "target=", "arch=", "git-commit=", "no-copy-python",
+        "universal", "target=", "arch=", "git-commit=", "no-copy-python",
         "cggl-incdir=", "cggl-libdir=",
         ] + removedopts
 
     anything = 0
     optimize = ""
     target = None
-    target_arch = None
+    target_archs = []
+    universal = False
     clean_build = False
     for pkg in PkgListGet():
         longopts.append("use-" + pkg.lower())
@@ -208,9 +211,9 @@ def parseopts(args):
             elif (option=="--nothing"): PkgDisableAll()
             elif (option=="--threads"): THREADCOUNT=int(value)
             elif (option=="--outputdir"): SetOutputDir(value.strip())
-            elif (option=="--osxtarget"): OSXTARGET=value.strip()
+            elif (option=="--universal"): universal = True
             elif (option=="--target"): target = value.strip()
-            elif (option=="--arch"): target_arch = value.strip()
+            elif (option=="--arch"): target_archs.append(value.strip())
             elif (option=="--nocolor"): DisableColors()
             elif (option=="--version"):
                 match = re.match(r'^\d+\.\d+(\.\d+)+', value)
@@ -239,7 +242,7 @@ def parseopts(args):
             elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True
             elif (option=="--clean"): clean_build = True
             elif (option=="--no-copy-python"): COPY_PYTHON = False
-            elif (option[2:] in removedopts):
+            elif (option[2:] in removedopts or option[2:]+'=' in removedopts):
                 Warn("Ignoring removed option %s" % (option))
             else:
                 for pkg in PkgListGet() + ['CGGL']:
@@ -268,29 +271,17 @@ def parseopts(args):
 
     if (optimize==""): optimize = "3"
 
-    if OSXTARGET:
-        try:
-            maj, min = OSXTARGET.strip().split('.')
-            OSXTARGET = int(maj), int(min)
-            assert OSXTARGET[0] >= 10
-        except:
-            usage("Invalid setting for OSXTARGET")
+    if target is not None or target_archs:
+        SetTarget(target, target_archs[-1] if target_archs else None)
 
-        if OSXTARGET < (10, 9):
-            warn_prefix = "%sERROR:%s " % (GetColor("red"), GetColor())
-            print("=========================================================================")
-            print(warn_prefix + "Support for macOS versions before 10.9 has been discontinued.")
-            print(warn_prefix + "For more information, or any questions, please visit:")
-            print("  https://github.com/panda3d/panda3d/issues/300")
-            print("=========================================================================")
-            sys.stdout.flush()
-            time.sleep(1.0)
-            sys.exit(1)
-    else:
-        OSXTARGET = None
+    if universal:
+        if target_archs:
+            exit("--universal is incompatible with --arch")
 
-    if target is not None or target_arch is not None:
-        SetTarget(target, target_arch)
+        OSX_ARCHS.append("x86_64")
+        OSX_ARCHS.append("arm64")
+    elif target_archs:
+        OSX_ARCHS = target_archs
 
     try:
         SetOptimize(int(optimize))
@@ -350,8 +341,11 @@ if ("LDFLAGS" in os.environ):
     LDFLAGS = os.environ["LDFLAGS"].strip()
 
 os.environ["MAKEPANDA"] = os.path.abspath(sys.argv[0])
-if GetHost() == "darwin" and OSXTARGET is not None:
-    os.environ["MACOSX_DEPLOYMENT_TARGET"] = "%d.%d" % OSXTARGET
+if GetHost() == "darwin":
+    if tuple(OSX_ARCHS) == ('arm64',):
+        os.environ["MACOSX_DEPLOYMENT_TARGET"] = "11.0"
+    else:
+        os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
 
 ########################################################################
 ##
@@ -382,16 +376,30 @@ if target == 'windows':
         PLATFORM = 'win32'
 
 elif target == 'darwin':
-    if OSXTARGET:
-        osxver = OSXTARGET
+    arch_tag = None
+    if not OSX_ARCHS:
+        arch_tag = GetTargetArch()
+    elif len(OSX_ARCHS) == 1:
+        arch_tag = OSX_ARCHS[0]
+    elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')):
+        arch_tag = 'fat'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386')):
+        arch_tag = 'intel'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'ppc64')):
+        arch_tag = 'fat64'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc')):
+        arch_tag = 'fat32'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc64', 'ppc')):
+        arch_tag = 'universal'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'arm64')):
+        arch_tag = 'universal2'
     else:
-        maj, min = platform.mac_ver()[0].split('.')[:2]
-        osxver = int(maj), int(min)
-        if osxver < (10, 9):
-            osxver = (10, 9)
+        raise RuntimeError('No arch tag for arch combination %s' % OSX_ARCHS)
 
-    arch_tag = GetTargetArch()
-    PLATFORM = 'macosx-{0}.{1}-{2}'.format(osxver[0], osxver[1], arch_tag)
+    if arch_tag == 'arm64':
+        PLATFORM = 'macosx-11.0-' + arch_tag
+    else:
+        PLATFORM = 'macosx-10.9-' + arch_tag
 
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
     # This is manylinux1.  A bit of a sloppy check, though.
@@ -474,7 +482,7 @@ MakeBuildTree()
 SdkLocateDirectX(STRDXSDKVERSION)
 SdkLocateMaya()
 SdkLocateMax()
-SdkLocateMacOSX(OSXTARGET)
+SdkLocateMacOSX(OSX_ARCHS)
 SdkLocatePython(False)
 SdkLocateWindows(WINDOWS_SDK)
 SdkLocateSpeedTree()
@@ -747,6 +755,24 @@ if (COMPILER=="GCC"):
     if GetTarget() != "darwin":
         PkgDisable("COCOA")
 
+    if GetTarget() == 'darwin':
+        if OSX_ARCHS and 'x86_64' not in OSX_ARCHS and 'i386' not in OSX_ARCHS:
+            # These support only these archs, so don't build them if we're not
+            # targeting any of the supported archs.
+            PkgDisable("FMODEX")
+            PkgDisable("NVIDIACG")
+        elif OSX_ARCHS and 'arm64' in OSX_ARCHS:
+            # We must be using the 11.0 SDK or higher, so can't build FMOD Ex
+            if not PkgSkip("FMODEX"):
+                Warn("thirdparty package fmodex is not supported when targeting arm64, excluding from build")
+            PkgDisable("FMODEX")
+        elif not os.path.isfile(SDK.get("MACOSX", "") + '/usr/lib/libstdc++.6.0.9.tbd') and \
+             not os.path.isfile(SDK.get("MACOSX", "") + '/usr/lib/libstdc++.6.0.9.dylib'):
+            # Also, we can't target FMOD Ex on 10.14 and above
+            if not PkgSkip("FMODEX"):
+                Warn("thirdparty package fmodex requires one of MacOSX 10.9-10.13 SDK, excluding from build")
+            PkgDisable("FMODEX")
+
     #if (PkgSkip("PYTHON")==0):
     #    IncDirectory("PYTHON", SDK["PYTHON"])
     if (GetHost() == "darwin"):
@@ -1275,15 +1301,20 @@ def CompileCxx(obj,src,opts):
         # Mac-specific flags.
         if GetTarget() == "darwin":
             cmd += " -Wno-deprecated-declarations"
-            if OSXTARGET is not None:
+            if SDK.get("MACOSX"):
                 cmd += " -isysroot " + SDK["MACOSX"]
-                cmd += " -mmacosx-version-min=%d.%d" % (OSXTARGET)
+
+            if tuple(OSX_ARCHS) == ('arm64',):
+                cmd += " -mmacosx-version-min=11.0"
+            else:
+                cmd += " -mmacosx-version-min=10.9"
 
             # Use libc++ to enable C++11 features.
             cmd += " -stdlib=libc++"
 
-            arch = GetTargetArch()
-            cmd += " -arch %s" % arch
+            for arch in OSX_ARCHS:
+                if 'NOARCH:' + arch.upper() not in opts:
+                    cmd += " -arch %s" % arch
 
         if "SYSROOT" in SDK:
             if GetTarget() != "android":
@@ -1814,15 +1845,20 @@ def CompileLink(dll, obj, opts):
         # macOS specific flags.
         if GetTarget() == 'darwin':
             cmd += " -headerpad_max_install_names"
-            if OSXTARGET is not None:
+            if SDK.get("MACOSX"):
                 cmd += " -isysroot " + SDK["MACOSX"] + " -Wl,-syslibroot," + SDK["MACOSX"]
-                cmd += " -mmacosx-version-min=%d.%d" % (OSXTARGET)
+
+            if tuple(OSX_ARCHS) == ('arm64',):
+                cmd += " -mmacosx-version-min=11.0"
+            else:
+                cmd += " -mmacosx-version-min=10.9"
 
             # Use libc++ to enable C++11 features.
             cmd += " -stdlib=libc++"
 
-            arch = GetTargetArch()
-            cmd += " -arch %s" % arch
+            for arch in OSX_ARCHS:
+                if 'NOARCH:' + arch.upper() not in opts:
+                    cmd += " -arch %s" % arch
 
         elif GetTarget() == 'android':
             arch = GetTargetArch()
@@ -1942,7 +1978,11 @@ def CompileEgg(eggfile, src, opts):
         oscmd(flt2egg + ' -ps keep -o ' + BracketNameWithQuotes(eggfile) + ' ' + BracketNameWithQuotes(src))
 
     if pz:
-        oscmd(pzip + ' ' + BracketNameWithQuotes(eggfile))
+        if zlib:
+            WriteBinaryFile(eggfile + '.pz', zlib.compress(ReadBinaryFile(eggfile)))
+            os.remove(eggfile)
+        else:
+            oscmd(pzip + ' ' + BracketNameWithQuotes(eggfile))
 
 ##########################################################################################
 #
@@ -2517,8 +2557,19 @@ def WriteConfigSettings():
     conf = "/* dtool_config.h.  Generated automatically by makepanda.py */\n"
     for key in sorted(dtool_config.keys()):
         val = OverrideValue(key, dtool_config[key])
-        if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
-        else:                conf = conf + "#define " + key + " " + val + "\n"
+
+        if key in ('HAVE_CG', 'HAVE_CGGL', 'HAVE_CGDX9') and val != 'UNDEF':
+            # These are not available for ARM, period.
+            conf = conf + "#ifdef __aarch64__\n"
+            conf = conf + "#undef " + key + "\n"
+            conf = conf + "#else\n"
+            conf = conf + "#define " + key + " " + val + "\n"
+            conf = conf + "#endif\n"
+        elif val == 'UNDEF':
+            conf = conf + "#undef " + key + "\n"
+        else:
+            conf = conf + "#define " + key + " " + val + "\n"
+
     ConditionalWriteFile(GetOutputDir() + '/include/dtool_config.h', conf)
 
     if (PkgSkip("SPEEDTREE")==0):
@@ -2876,13 +2927,18 @@ if tp_dir is not None:
     if GetTarget() == 'darwin':
         # Make a list of all the dylibs we ship, to figure out whether we should use
         # install_name_tool to correct the library reference to point to our copy.
-        for lib in glob.glob(tp_dir + "/*/lib/*.dylib"):
-            dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
+        for pkg in PkgListGet():
+            if PkgSkip(pkg):
+                continue
 
-        if not PkgSkip("PYTHON"):
-            for lib in glob.glob(tp_dir + "/*/lib/" + SDK["PYTHONVERSION"] + "/*.dylib"):
+            tp_libdir = os.path.join(tp_dir, pkg.lower(), "lib")
+            for lib in glob.glob(os.path.join(tp_libdir, "*.dylib")):
                 dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
 
+            if not PkgSkip("PYTHON"):
+                for lib in glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.dylib")):
+                    dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
+
     for pkg in PkgListGet():
         if PkgSkip(pkg):
             continue
@@ -2895,13 +2951,14 @@ if tp_dir is not None:
                     CopyAllFiles(GetOutputDir() + "/bin/", tp_pkg + "/bin/" + SDK["PYTHONVERSION"] + "/")
 
         elif GetTarget() == 'darwin':
-            tp_libs = glob.glob(tp_pkg + "/lib/*.dylib")
+            tp_libdir = os.path.join(tp_pkg, "lib")
+            tp_libs = glob.glob(os.path.join(tp_libdir, "*.dylib"))
 
             if not PkgSkip("PYTHON"):
-                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.dylib"))
-                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.so"))
+                tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.dylib"))
+                tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.so"))
                 if pkg != 'PYTHON':
-                    tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.py"))
+                    tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.py"))
 
             for tp_lib in tp_libs:
                 basename = os.path.basename(tp_lib)
@@ -2942,6 +2999,7 @@ if tp_dir is not None:
                 JustBuilt([target], [tp_lib])
 
             for fwx in glob.glob(tp_pkg + "/*.framework"):
+                MakeDirectory(GetOutputDir() + "/Frameworks")
                 CopyTree(GetOutputDir() + "/Frameworks/" + os.path.basename(fwx), fwx)
 
         else:  # Linux / FreeBSD case.
@@ -5823,20 +5881,33 @@ if not PkgSkip("PANDATOOL"):
 # DIRECTORY: pandatool/src/mayaprogs/
 #
 
+MAYA_BUILT = False
+
 for VER in MAYAVERSIONS:
-  VNUM = VER[4:]
-  if not PkgSkip(VER) and not PkgSkip("PANDATOOL") and not PkgSkip("EGG"):
-    if GetTarget() == 'darwin' and int(VNUM) < 2009:
-      # No x86_64 support.
-      continue
+    VNUM = VER[4:]
+    if PkgSkip(VER) or PkgSkip("PANDATOOL") or PkgSkip("EGG"):
+        continue
+
+    if GetTarget() == 'darwin':
+        if int(VNUM) < 2009:
+            # No x86_64 support.
+            continue
+        if tuple(OSX_ARCHS) == ('arm64',):
+            # No arm64 support.
+            continue
+        ARCH_OPTS = ['NOARCH:ARM64']
+    else:
+        ARCH_OPTS = []
 
-    OPTS=['DIR:pandatool/src/mayaprogs', 'DIR:pandatool/src/maya', 'DIR:pandatool/src/mayaegg', 'BUILDING:MISC', VER]
+    MAYA_BUILT = True
+
+    OPTS=['DIR:pandatool/src/mayaprogs', 'DIR:pandatool/src/maya', 'DIR:pandatool/src/mayaegg', 'BUILDING:MISC', VER] + ARCH_OPTS
     TargetAdd('mayaeggimport'+VNUM+'_mayaeggimport.obj', opts=OPTS, input='mayaEggImport.cxx')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaegg'+VNUM+'_loader.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaeggimport'+VNUM+'_mayaeggimport.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='libpandaegg.dll')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input=COMMON_PANDA_LIBS)
-    TargetAdd('mayaeggimport'+VNUM+'.mll', opts=['ADVAPI', VER])
+    TargetAdd('mayaeggimport'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
     TargetAdd('mayaloader'+VNUM+'_config_mayaloader.obj', opts=OPTS, input='config_mayaloader.cxx')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='mayaloader'+VNUM+'_config_mayaloader.obj')
@@ -5860,54 +5931,68 @@ for VER in MAYAVERSIONS:
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libp3pandatoolbase.lib')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libpandaegg.dll')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input=COMMON_PANDA_LIBS)
-    TargetAdd('libp3mayaloader'+VNUM+'.dll', opts=['ADVAPI', VER])
+    TargetAdd('libp3mayaloader'+VNUM+'.dll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
     TargetAdd('mayapview'+VNUM+'_mayaPview.obj', opts=OPTS, input='mayaPview.cxx')
     TargetAdd('libmayapview'+VNUM+'.mll', input='mayapview'+VNUM+'_mayaPview.obj')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmaya'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libp3framework.dll')
-    if GetTarget() == 'windows':
-      TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
-    TargetAdd('libmayapview'+VNUM+'.mll', opts=['ADVAPI', VER])
-
-    TargetAdd('maya2egg'+VNUM+'_mayaToEgg.obj', opts=OPTS, input='mayaToEgg.cxx')
-    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='maya2egg'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
+    TargetAdd('libmayapview'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
+
+    TargetAdd('mayaprogs'+VNUM+'_eggToMaya.obj', opts=OPTS, input='eggToMaya.cxx')
+    TargetAdd('mayaprogs'+VNUM+'_mayaToEgg.obj', opts=OPTS, input='mayaToEgg.cxx')
+    TargetAdd('mayaprogs_mayaConversionServer.obj', opts=OPTS, input='mayaConversionServer.cxx')
+
+    TargetAdd('maya2egg'+VNUM+'_mayaToEggBin.obj', opts=OPTS, input='mayaToEggBin.cxx')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_eggToMaya.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs_mayaConversionServer.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='maya2egg'+VNUM+'_mayaToEggBin.obj')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
-    if GetTarget() == 'windows':
-      TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    TargetAdd('maya2egg'+VNUM+'_bin.exe', opts=['ADVAPI', VER])
-
-    TargetAdd('egg2maya'+VNUM+'_eggToMaya.obj', opts=OPTS, input='eggToMaya.cxx')
-    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='egg2maya'+VNUM+'_eggToMaya.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', opts=['ADVAPI', VER]+ARCH_OPTS)
+
+    TargetAdd('egg2maya'+VNUM+'_eggToMayaBin.obj', opts=OPTS, input='eggToMayaBin.cxx')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_eggToMaya.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs_mayaConversionServer.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='egg2maya'+VNUM+'_eggToMayaBin.obj')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
-    if GetTarget() == 'windows':
-      TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    TargetAdd('egg2maya'+VNUM+'_bin.exe', opts=['ADVAPI', VER])
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', opts=['ADVAPI', VER]+ARCH_OPTS)
 
     TargetAdd('mayasavepview'+VNUM+'_mayaSavePview.obj', opts=OPTS, input='mayaSavePview.cxx')
     TargetAdd('libmayasavepview'+VNUM+'.mll', input='mayasavepview'+VNUM+'_mayaSavePview.obj')
-    TargetAdd('libmayasavepview'+VNUM+'.mll', opts=['ADVAPI', VER])
+    TargetAdd('libmayasavepview'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
     TargetAdd('mayapath'+VNUM+'.obj', opts=OPTS, input='mayapath.cxx')
 
     TargetAdd('maya2egg'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('maya2egg'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('maya2egg'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
-    TargetAdd('maya2egg'+VNUM+'.exe', opts=['ADVAPI'])
+    TargetAdd('maya2egg'+VNUM+'.exe', opts=['ADVAPI']+ARCH_OPTS)
 
     TargetAdd('egg2maya'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('egg2maya'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('egg2maya'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
-    TargetAdd('egg2maya'+VNUM+'.exe', opts=['ADVAPI'])
+    TargetAdd('egg2maya'+VNUM+'.exe', opts=['ADVAPI']+ARCH_OPTS)
+
+if MAYA_BUILT:
+    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('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)
 
 #
 # DIRECTORY: contrib/src/ai/

+ 34 - 24
makepanda/makepandacore.py

@@ -389,6 +389,9 @@ def SetTarget(target, arch=None):
             else:
                 arch = 'armv7a'
 
+        if arch == 'arm64':
+            arch = 'aarch64'
+
         # Did we specify an API level?
         global ANDROID_API
         target, _, api = target.partition('-')
@@ -1251,10 +1254,7 @@ def MakeBuildTree():
     MakeDirectory(OUTPUTDIR + "/pandac/input")
     MakeDirectory(OUTPUTDIR + "/panda3d")
 
-    if GetTarget() == 'darwin':
-        MakeDirectory(OUTPUTDIR + "/Frameworks")
-
-    elif GetTarget() == 'android':
+    if GetTarget() == 'android':
         MakeDirectory(OUTPUTDIR + "/classes")
 
 ########################################################################
@@ -2362,30 +2362,40 @@ def SdkLocateWindows(version=None):
     else:
         print("Using Windows SDK %s" % (version))
 
-def SdkLocateMacOSX(osxtarget = None):
+def SdkLocateMacOSX(archs = []):
     if (GetHost() != "darwin"): return
-    if (osxtarget != None):
-        sdkname = "MacOSX%d.%d" % osxtarget
-        if (os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname)):
+
+    handle = os.popen("xcode-select -print-path")
+    xcode_dir = handle.read().strip().rstrip('/')
+    handle.close()
+
+    # Make a list of SDK versions that will work for us, then grab the latest.
+    sdk_versions = []
+    if 'arm64' not in archs:
+        # Prefer pre-10.14 for now so that we can keep building FMOD.
+        sdk_versions += ["10.13", "10.12", "10.11", "10.10", "10.9"]
+
+    sdk_versions += ["11.1", "11.0"]
+
+    if 'arm64' not in archs:
+        sdk_versions += ["10.15", "10.14"]
+
+    for version in sdk_versions:
+        sdkname = "MacOSX" + version
+        if os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%su.sdk" % sdkname)):
-            SDK["MACOSX"] = "/Developer/SDKs/%su.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%s.sdk" % sdkname)):
+            return
+        elif os.path.exists("/Developer/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Developer/SDKs/%s.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%s.0.sdk" % sdkname)):
-            SDK["MACOSX"] = "/Developer/SDKs/%s.0.sdk" % sdkname
-        elif (os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname)):
+            return
+        elif os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname
-        else:
-            handle = os.popen("xcode-select -print-path")
-            result = handle.read().strip().rstrip('/')
-            handle.close()
-            if (os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (result, sdkname))):
-                SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (result, sdkname)
-            else:
-                exit("Couldn't find any MacOSX SDK for OSX version %s!" % sdkname)
-    else:
-        SDK["MACOSX"] = ""
+            return
+        elif xcode_dir and os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)):
+            SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)
+            return
+
+    exit("Couldn't find any suitable MacOSX SDK!")
 
 def SdkLocateSpeedTree():
     # Look for all of the SpeedTree SDK directories within the

+ 1 - 1
makepanda/makewheel.py

@@ -458,7 +458,7 @@ class WheelFile(object):
                         self.consider_add_dependency(target_dep, dep)
 
                 subprocess.call(["strip", "-s", temp.name])
-                subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name])
+                subprocess.call(["patchelf", "--force-rpath", "--set-rpath", "$ORIGIN", temp.name])
 
             source_path = temp.name
 

+ 8 - 0
panda/src/audio/audioSound.h

@@ -123,6 +123,14 @@ PUBLISHED:
   virtual void output(std::ostream &out) const;
   virtual void write(std::ostream &out) const;
 
+PUBLISHED:
+  MAKE_PROPERTY(time, get_time, set_time);
+  MAKE_PROPERTY(volume, get_volume, set_volume);
+  MAKE_PROPERTY(balance, get_balance, set_balance);
+  MAKE_PROPERTY(play_rate, get_play_rate, set_play_rate);
+  MAKE_PROPERTY(active, get_active, set_active);
+  MAKE_PROPERTY(name, get_name);
+
 protected:
   AudioSound();
 

+ 12 - 1
panda/src/audiotraits/CMakeLists.txt

@@ -5,7 +5,7 @@ elseif(NOT HAVE_FMODEX AND NOT HAVE_OPENAL)
     "You must have an audio backend for audio support! Turn off HAVE_AUDIO to ignore this.")
 endif()
 
-if(HAVE_FMODEX)
+if(HAVE_FMODEX AND NOT (APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64"))
   set(P3FMOD_HEADERS
     config_fmodAudio.h
     fmodAudioManager.h
@@ -21,6 +21,17 @@ if(HAVE_FMODEX)
   set_target_properties(p3fmod_audio PROPERTIES DEFINE_SYMBOL BUILDING_FMOD_AUDIO)
   target_link_libraries(p3fmod_audio panda PKG::FMODEX)
 
+  if(APPLE)
+    # The FMOD EX thirdparty library is not available for arm64, so we have to
+    # exclude it from the target architectures.
+    get_target_property(_archs p3fmod_audio OSX_ARCHITECTURES)
+
+    if(_archs AND _archs MATCHES "arm64")
+      list(REMOVE_ITEM _archs "arm64")
+      set_target_properties(p3fmod_audio PROPERTIES OSX_ARCHITECTURES "${_archs}")
+    endif()
+  endif()
+
   install(TARGETS p3fmod_audio
     EXPORT FMOD COMPONENT FMOD
     DESTINATION ${CMAKE_INSTALL_LIBDIR}

+ 4 - 2
panda/src/audiotraits/fmodAudioSound.cxx

@@ -259,6 +259,7 @@ stop() {
     }
   }
   _start_time = 0.0;
+  _paused = false;
 }
 
 
@@ -827,12 +828,13 @@ set_active(bool active) {
     } else {
       // ...deactivate the sound.
       if (status() == PLAYING) {
+        PN_stdfloat time = get_time();
+        stop();
         if (get_loop_count() == 0) {
           // ...we're pausing a looping sound.
           _paused = true;
-          _start_time = get_time();
+          _start_time = time;
         }
-        stop();
       }
     }
   }

+ 6 - 4
panda/src/audiotraits/openalAudioSound.cxx

@@ -217,6 +217,8 @@ stop() {
     _stream_queued.resize(0);
   }
 
+  _paused = false;
+
   _manager->stopping_sound(this);
   release_sound_data(false);
 }
@@ -828,13 +830,13 @@ set_active(bool active) {
     } else {
       // ...deactivate the sound.
       if (status()==PLAYING) {
-        if (_loop_count==0) {
-          // ...we're pausing a looping sound.
-          _paused=true;
-        }
         // Store off the current time so we can resume from where we paused.
         _start_time = get_time();
         stop();
+        if (_loop_count == 0) {
+          // ...we're pausing a looping sound.
+          _paused = true;
+        }
       }
     }
   }

+ 3 - 0
panda/src/cocoadisplay/CMakeLists.txt

@@ -42,6 +42,9 @@ target_link_libraries(p3cocoadisplay
   ${APPLICATIONSERVICES_LIBRARY} ${APPKIT_LIBRARY} ${CARBON_LIBRARY}
   ${CORE_VIDEO_LIBRARY})
 
+mark_as_advanced(
+  APPLICATIONSERVICES_LIBRARY APPKIT_LIBRARY CARBON_LIBRARY CORE_VIDEO_LIBRARY)
+
 if(NOT BUILD_METALIBS)
   install(TARGETS p3cocoadisplay EXPORT OpenGL COMPONENT OpenGL DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()

+ 28 - 12
panda/src/collide/collisionPolygon_ext.cxx

@@ -29,7 +29,11 @@ extern struct Dtool_PyTypedObject Dtool_LPoint3f;
  */
 bool Extension<CollisionPolygon>::
 verify_points(PyObject *points) {
-  const pvector<LPoint3> vec = convert_points(points);
+  pvector<LPoint3> vec;
+  if (!convert_points(vec, points)) {
+    return false;
+  }
+
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_end = verts_begin + vec.size();
 
@@ -42,7 +46,16 @@ verify_points(PyObject *points) {
  */
 void Extension<CollisionPolygon>::
 setup_points(PyObject *points) {
-  const pvector<LPoint3> vec = convert_points(points);
+  pvector<LPoint3> vec;
+  if (!convert_points(vec, points)) {
+    return;
+  }
+
+  if (vec.size() < 3) {
+    PyErr_SetString(PyExc_ValueError, "expected at least 3 points");
+    return;
+  }
+
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_end = verts_begin + vec.size();
 
@@ -52,13 +65,11 @@ setup_points(PyObject *points) {
 /**
  * Converts a Python sequence to a list of LPoint3 objects.
  */
-pvector<LPoint3> Extension<CollisionPolygon>::
-convert_points(PyObject *points) {
-  pvector<LPoint3> vec;
+bool Extension<CollisionPolygon>::
+convert_points(pvector<LPoint3> &vec, PyObject *points) {
   PyObject *seq = PySequence_Fast(points, "function expects a sequence");
-
   if (!seq) {
-    return vec;
+    return false;
   }
 
   PyObject **items = PySequence_Fast_ITEMS(seq);
@@ -69,18 +80,23 @@ convert_points(PyObject *points) {
 
   for (Py_ssize_t i = 0; i < len; ++i) {
 #ifdef STDFLOAT_DOUBLE
-    if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3d)) {
+    if (DtoolInstance_Check(items[i]) &&
+        (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3d))) {
 #else
-    if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3f)) {
+    if (DtoolInstance_Check(items[i]) &&
+        (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3f))) {
 #endif
       vec.push_back(*(LPoint3 *)ptr);
-    } else {
-      collide_cat.warning() << "Argument must be of LPoint3 type.\n";
+    }
+    else {
+      Dtool_Raise_TypeError("Argument must be of LPoint3 type.");
+      Py_DECREF(seq);
+      return false;
     }
   }
 
   Py_DECREF(seq);
-  return vec;
+  return true;
 }
 
 #endif

+ 1 - 1
panda/src/collide/collisionPolygon_ext.h

@@ -35,7 +35,7 @@ public:
   void setup_points(PyObject *points);
 
 private:
-  static pvector<LPoint3> convert_points(PyObject *points);
+  static bool convert_points(pvector<LPoint3> &vec, PyObject *points);
 };
 
 #endif  // HAVE_PYTHON

+ 22 - 12
panda/src/device/evdevInputDevice.cxx

@@ -806,23 +806,33 @@ process_events() {
         button_changed(_dpad_up_button, events[i].value < 0);
         button_changed(_dpad_up_button+1, events[i].value > 0);
       }
-      nassertd(code >= 0 && (size_t)code < _axis_indices.size()) break;
-      index = _axis_indices[code];
-      if (index >= 0) {
-        axis_changed(index, events[i].value);
+      if (code >= 0 && (size_t)code < _axis_indices.size()) {
+        index = _axis_indices[code];
+        if (index >= 0) {
+          axis_changed(index, events[i].value);
+        }
+      }
+      else if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Ignoring EV_ABS event with unknown code " << code << "\n";
       }
       break;
 
     case EV_KEY:
-      nassertd(code >= 0 && (size_t)code < _button_indices.size()) break;
-      index = _button_indices[code];
-      if (index >= 0) {
-        button_changed(index, events[i].value != 0);
+      if (code >= 0 && (size_t)code < _button_indices.size()) {
+        index = _button_indices[code];
+        if (index >= 0) {
+          button_changed(index, events[i].value != 0);
+        }
+        if (code == _ltrigger_code) {
+          axis_changed(_ltrigger_axis, events[i].value);
+        } else if (code == _rtrigger_code) {
+          axis_changed(_ltrigger_axis + 1, events[i].value);
+        }
       }
-      if (code == _ltrigger_code) {
-        axis_changed(_ltrigger_axis, events[i].value);
-      } else if (code == _rtrigger_code) {
-        axis_changed(_ltrigger_axis + 1, events[i].value);
+      else if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Ignoring EV_KEY event with unknown code " << code << "\n";
       }
       break;
 

+ 2 - 0
panda/src/device/inputDeviceManager.cxx

@@ -111,6 +111,8 @@ add_device(InputDevice *device) {
  */
 void InputDeviceManager::
 remove_device(InputDevice *device) {
+  // We need to hold a reference, since remove_device decrements the refcount.
+  PT(InputDevice) device_ref = device;
   {
     LightMutexHolder holder(_lock);
     _connected_devices.remove_device(device);

+ 8 - 0
panda/src/display/config_display.cxx

@@ -485,6 +485,14 @@ ConfigVariableBool sync_video
           "cheesy estimate of scene complexity.  Some drivers may ignore "
           "this request."));
 
+ConfigVariableDouble display_zoom
+("display-zoom", 0.0,
+ PRC_DESC("If this is set to a value other than 0.0, it overrides the detected "
+          "system DPI scaling.  GraphicsPipe::get_display_zoom() will instead "
+          "return whatever was passed in here.  You should generally only "
+          "change this based on a user preference change or to test how the UI "
+          "will look on monitors with different pixel densities."));
+
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be

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

@@ -109,6 +109,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableDouble pixel_zoom;
 
 extern EXPCL_PANDA_DISPLAY ConfigVariableColor background_color;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool sync_video;
+extern EXPCL_PANDA_DISPLAY ConfigVariableDouble display_zoom;
 
 extern EXPCL_PANDA_DISPLAY void init_libdisplay();
 

+ 8 - 0
panda/src/display/graphicsPipe.I

@@ -81,3 +81,11 @@ INLINE GraphicsDevice *GraphicsPipe::
 get_device() const {
   return _device;
 }
+
+/**
+ * Called by derived class to set the display zoom factor.
+ */
+INLINE void GraphicsPipe::
+set_detected_display_zoom(PN_stdfloat zoom) {
+  _detected_display_zoom = zoom;
+}

+ 22 - 0
panda/src/display/graphicsPipe.cxx

@@ -116,6 +116,7 @@ GraphicsPipe() :
 
   _display_width = 0;
   _display_height = 0;
+  _detected_display_zoom = 1.0;
 
   _display_information = new DisplayInformation();
 
@@ -266,6 +267,27 @@ make_output(const std::string &name,
   return nullptr;
 }
 
+/**
+ * Returns the display zoom factor configured in the operating system.  If the
+ * operating system automatically scales windows to match the DPI (such as when
+ * dpi-aware is set to false), this will be 1.0.  Otherwise, this will be set to
+ * a value approximating the density of the monitor divided by the standard
+ * density of the operating system (usually 96), yielding a value like 1.5 or
+ * 2.0.
+ *
+ * @since 1.10.8
+ */
+PN_stdfloat GraphicsPipe::
+get_display_zoom() const {
+  if (display_zoom.get_num_words() > 0) {
+    double override = display_zoom.get_value();
+    if (override != 0.0) {
+      return override;
+    }
+  }
+  return _detected_display_zoom;
+}
+
 /**
  * Gets the pipe's DisplayInformation.
  */

+ 5 - 0
panda/src/display/graphicsPipe.h

@@ -91,8 +91,10 @@ PUBLISHED:
 
   INLINE int get_display_width() const;
   INLINE int get_display_height() const;
+  PN_stdfloat get_display_zoom() const;
   MAKE_PROPERTY(display_width, get_display_width);
   MAKE_PROPERTY(display_height, get_display_height);
+  MAKE_PROPERTY(display_zoom, get_display_zoom);
 
   DisplayInformation *get_display_information();
   MAKE_PROPERTY(display_information, get_display_information);
@@ -115,6 +117,8 @@ public:
   virtual PT(GraphicsStateGuardian) make_callback_gsg(GraphicsEngine *engine);
 
 protected:
+  INLINE void set_detected_display_zoom(PN_stdfloat zoom);
+
   virtual void close_gsg(GraphicsStateGuardian *gsg);
 
   virtual PT(GraphicsOutput) make_output(const std::string &name,
@@ -133,6 +137,7 @@ protected:
   int _supported_types;
   int _display_width;
   int _display_height;
+  PN_stdfloat _detected_display_zoom;
   PT(GraphicsDevice) _device;
 
   DisplayInformation *_display_information;

+ 197 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -1911,6 +1911,10 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
 PT(Texture) GraphicsStateGuardian::
 fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
                         int &view) {
+
+  static PT(Texture) default_add_tex;
+  static PT(Texture) default_normal_height_tex;
+
   switch (spec._part) {
   case Shader::STO_named_input:
     // Named texture input.
@@ -2008,6 +2012,199 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
     }
     break;
 
+  case Shader::STO_ff_stage_i:
+    {
+      // We get the TextureAttrib directly from the _target_rs, not the
+      // filtered TextureAttrib in _target_texture.
+      const TextureAttrib *texattrib;
+      _target_rs->get_attrib_def(texattrib);
+
+      if (spec._stage < texattrib->get_num_on_ff_stages()) {
+        TextureStage *stage = texattrib->get_on_ff_stage(spec._stage);
+        sampler = texattrib->get_on_sampler(stage);
+        view += stage->get_tex_view_offset();
+        return texattrib->get_on_texture(stage);
+      }
+    }
+    break;
+
+  case Shader::STO_stage_modulate_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_modulate ||
+              mode == TextureStage::M_modulate_glow ||
+              mode == TextureStage::M_modulate_gloss) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_add_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_add) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_add_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-add");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_luminance);
+        tex->set_clear_color(LColor(0, 0, 0, 1));
+        default_add_tex = std::move(tex);
+      }
+      return default_add_tex;
+    }
+    break;
+
+  case Shader::STO_stage_normal_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_normal ||
+              mode == TextureStage::M_normal_height) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_normal_height_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-normal-height");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+        tex->set_clear_color(LColor(0.5, 0.5, 1, 0));
+        default_normal_height_tex = std::move(tex);
+      }
+      return default_normal_height_tex;
+    }
+    break;
+
+  case Shader::STO_stage_gloss_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_gloss ||
+              mode == TextureStage::M_modulate_gloss ||
+              mode == TextureStage::M_normal_gloss) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_height_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_height ||
+              mode == TextureStage::M_normal_height) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_normal_height_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-normal-height");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+        tex->set_clear_color(LColor(0.5, 0.5, 1, 0));
+        default_normal_height_tex = std::move(tex);
+      }
+      return default_normal_height_tex;
+    }
+    break;
+
+  case Shader::STO_stage_selector_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_selector) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_emission_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_emission) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
   default:
     nassertr(false, nullptr);
     break;

+ 7 - 0
panda/src/egg/eggTexture.cxx

@@ -558,6 +558,7 @@ affects_polygon_alpha() const {
   case ET_gloss:
   case ET_height:
   case ET_normal_gloss:
+  case ET_emission:
     return false;
 
   case ET_selector:
@@ -882,6 +883,9 @@ string_env_type(const string &string) {
   } else if (cmp_nocase_uh(string, "normal_gloss") == 0) {
     return ET_normal_gloss;
 
+  } else if (cmp_nocase_uh(string, "emission") == 0) {
+    return ET_emission;
+
   } else {
     return ET_unspecified;
   }
@@ -1312,6 +1316,9 @@ ostream &operator << (ostream &out, EggTexture::EnvType type) {
 
   case EggTexture::ET_normal_gloss:
     return out << "normal_gloss";
+
+  case EggTexture::ET_emission:
+    return out << "emission";
   }
 
   nassertr(false, out);

+ 1 - 0
panda/src/egg/eggTexture.h

@@ -107,6 +107,7 @@ PUBLISHED:
     ET_height,
     ET_selector,
     ET_normal_gloss,
+    ET_emission,
   };
   enum CombineMode {
     CM_unspecified,

+ 4 - 0
panda/src/egg2pg/eggLoader.cxx

@@ -1540,6 +1540,10 @@ make_texture_stage(const EggTexture *egg_tex) {
     stage->set_mode(TextureStage::M_normal_gloss);
     break;
 
+  case EggTexture::ET_emission:
+    stage->set_mode(TextureStage::M_emission);
+    break;
+
   case EggTexture::ET_unspecified:
     break;
   }

+ 3 - 0
panda/src/egg2pg/eggSaver.cxx

@@ -855,6 +855,9 @@ convert_primitive(const GeomVertexData *vertex_data,
         case TextureStage::M_normal_gloss:
           egg_tex->set_env_type(EggTexture::ET_normal_gloss);
           break;
+        case TextureStage::M_emission:
+          egg_tex->set_env_type(EggTexture::ET_emission);
+          break;
         default:
           break;
         }

+ 6 - 6
panda/src/event/pythonTask.cxx

@@ -266,11 +266,11 @@ exception() const {
     Py_INCREF(Py_None);
     return Py_None;
   } else if (_exc_value == nullptr || _exc_value == Py_None) {
-    return _PyObject_CallNoArg(_exception);
+    return PyObject_CallNoArgs(_exception);
   } else if (PyTuple_Check(_exc_value)) {
     return PyObject_Call(_exception, _exc_value, nullptr);
   } else {
-    return PyObject_CallFunctionObjArgs(_exception, _exc_value, nullptr);
+    return PyObject_CallOneArg(_exception, _exc_value);
   }
 }*/
 
@@ -481,7 +481,7 @@ do_python_task() {
 
   // Are we waiting for a future to finish?
   if (_future_done != nullptr) {
-    PyObject *is_done = PyObject_CallObject(_future_done, nullptr);
+    PyObject *is_done = PyObject_CallNoArgs(_future_done);
     if (!PyObject_IsTrue(is_done)) {
       // Nope, ask again next frame.
       Py_DECREF(is_done);
@@ -543,12 +543,12 @@ do_python_task() {
       // we need to be able to read the value from a StopIteration exception.
       PyObject *func = PyObject_GetAttrString(_generator, "send");
       nassertr(func != nullptr, DS_interrupt);
-      result = PyObject_CallFunctionObjArgs(func, Py_None, nullptr);
+      result = PyObject_CallOneArg(func, Py_None);
       Py_DECREF(func);
     } else {
       // Throw a CancelledError into the generator.
       _must_cancel = false;
-      PyObject *exc = _PyObject_CallNoArg(Extension<AsyncFuture>::get_cancelled_error_type());
+      PyObject *exc = PyObject_CallNoArgs(Extension<AsyncFuture>::get_cancelled_error_type());
       PyObject *func = PyObject_GetAttrString(_generator, "throw");
       result = PyObject_CallFunctionObjArgs(func, exc, nullptr);
       Py_DECREF(func);
@@ -892,7 +892,7 @@ call_function(PyObject *function) {
   if (function != Py_None) {
     this->ref();
     PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
-    PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
+    PyObject *result = PyObject_CallOneArg(function, self);
     Py_XDECREF(result);
     Py_DECREF(self);
   }

+ 3 - 0
panda/src/express/zipArchive.h

@@ -26,6 +26,9 @@
 #include "pvector.h"
 #include "vector_uchar.h"
 
+// Defined by Cocoa, conflicts with the definition below.
+#undef verify
+
 /**
  * A file that contains a set of files.
  */

+ 1 - 1
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -224,7 +224,7 @@ cleanup() {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
     avcodec_free_context(&_audio_ctx);
 #else
-    delete _audio_ctx;
+    av_free(_audio_ctx);
 #endif
   }
   _audio_ctx = nullptr;

+ 1 - 1
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -616,7 +616,7 @@ close_stream() {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
     avcodec_free_context(&_video_ctx);
 #else
-    delete _video_ctx;
+    av_free(_video_ctx);
 #endif
   }
   _video_ctx = nullptr;

+ 50 - 12
panda/src/glstuff/glShaderContext_src.cxx

@@ -963,27 +963,65 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
       _shader->cp_add_mat_spec(bind);
       return;
     }
-    if (size > 7 && noprefix.substr(0, 7) == "Texture") {
+    if (noprefix.compare(0, 7, "Texture") == 0) {
       Shader::ShaderTexSpec bind;
       bind._id = arg_id;
-      bind._part = Shader::STO_stage_i;
-      bind._name = 0;
 
-      string tail;
-      bind._stage = string_to_int(noprefix.substr(7), tail);
-      if (!tail.empty()) {
+      if (!get_sampler_texture_type(bind._desired_type, param_type)) {
         GLCAT.error()
-          << "Error parsing shader input name: unexpected '"
-          << tail << "' in '" << param_name << "'\n";
+          << "Could not bind texture input " << param_name << "\n";
         return;
       }
 
-      if (get_sampler_texture_type(bind._desired_type, param_type)) {
+      if (size > 7 && isdigit(noprefix[7])) {
+        // p3d_Texture0, p3d_Texture1, etc.
+        bind._part = Shader::STO_stage_i;
+
+        string tail;
+        bind._stage = string_to_int(noprefix.substr(7), tail);
+        if (!tail.empty()) {
+          GLCAT.error()
+            << "Error parsing shader input name: unexpected '"
+            << tail << "' in '" << param_name << "'\n";
+          return;
+        }
         _glgsg->_glUniform1i(p, _shader->_tex_spec.size());
         _shader->_tex_spec.push_back(bind);
-      } else {
-        GLCAT.error()
-          << "Could not bind texture input " << param_name << "\n";
+      }
+      else {
+        // p3d_Texture[] or p3d_TextureModulate[], etc.
+        if (size == 7) {
+          bind._part = Shader::STO_stage_i;
+        }
+        else if (noprefix.compare(7, string::npos, "FF") == 0) {
+          bind._part = Shader::STO_ff_stage_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Modulate") == 0) {
+          bind._part = Shader::STO_stage_modulate_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Add") == 0) {
+          bind._part = Shader::STO_stage_add_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Normal") == 0) {
+          bind._part = Shader::STO_stage_normal_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Height") == 0) {
+          bind._part = Shader::STO_stage_height_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Selector") == 0) {
+          bind._part = Shader::STO_stage_selector_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Gloss") == 0) {
+          bind._part = Shader::STO_stage_gloss_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Emission") == 0) {
+          bind._part = Shader::STO_stage_emission_i;
+        }
+
+        for (bind._stage = 0; bind._stage < param_size; ++bind._stage) {
+          _glgsg->_glUniform1i(p + bind._stage, _shader->_tex_spec.size());
+          _shader->_tex_spec.push_back(bind);
+        }
       }
       return;
     }

+ 23 - 0
panda/src/gobj/geom.cxx

@@ -1294,6 +1294,29 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
   return gc;
 }
 
+/**
+ * Returns true if the Geom is within the given view frustum.
+ */
+bool Geom::
+is_in_view(const BoundingVolume *view_frustum, Thread *current_thread) const {
+  CDLockedReader cdata(_cycler, current_thread);
+
+  if (cdata->_user_bounds != nullptr) {
+    const GeometricBoundingVolume *gbv = cdata->_user_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+  else if (!cdata->_internal_bounds_stale) {
+    const GeometricBoundingVolume *gbv = cdata->_internal_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+  else {
+    CDWriter cdataw(((Geom *)this)->_cycler, cdata, false);
+    compute_internal_bounds(cdataw, current_thread);
+    const GeometricBoundingVolume *gbv = cdataw->_internal_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+}
+
 /**
  * Actually draws the Geom with the indicated GSG, using the indicated vertex
  * data (which might have been pre-munged to support the GSG's needs).

+ 2 - 0
panda/src/gobj/geom.h

@@ -157,6 +157,8 @@ PUBLISHED:
                            GraphicsStateGuardianBase *gsg);
 
 public:
+  bool is_in_view(const BoundingVolume *view_frustum, Thread *current_thread) const;
+
   bool draw(GraphicsStateGuardianBase *gsg,
             const GeomVertexData *vertex_data, size_t num_instances,
             bool force, Thread *current_thread) const;

+ 8 - 0
panda/src/gobj/geomPrimitive.cxx

@@ -1627,6 +1627,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
 
       for (; i < cdata->_num_vertices; ++i) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
+        nassertv(!reader.is_at_end());
+
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
 
         min_point.set(min(min_point[0], vertex[0]),
@@ -1653,6 +1655,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
 
       for (; i < cdata->_num_vertices; ++i) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
+        nassertv(!reader.is_at_end());
+
         const LVecBase3 &vertex = reader.get_data3();
 
         min_point.set(min(min_point[0], vertex[0]),
@@ -1696,6 +1700,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
           continue;
         }
         reader.set_row_unsafe(ii);
+        nassertv(!reader.is_at_end());
+
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
 
         min_point.set(min(min_point[0], vertex[0]),
@@ -1728,6 +1734,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
           continue;
         }
         reader.set_row_unsafe(ii);
+        nassertv(!reader.is_at_end());
+
         const LVecBase3 &vertex = reader.get_data3();
 
         min_point.set(min(min_point[0], vertex[0]),

+ 22 - 9
panda/src/gobj/geomTristrips.cxx

@@ -482,15 +482,28 @@ requires_unused_vertices() const {
  */
 void GeomTristrips::
 append_unused_vertices(GeomVertexArrayData *vertices, int vertex) {
-  GeomVertexReader from(vertices, 0);
-  from.set_row_unsafe(vertices->get_num_rows() - 1);
-  int prev = from.get_data1i();
-
-  GeomVertexWriter to(vertices, 0);
-  to.set_row_unsafe(vertices->get_num_rows());
-
-  to.add_data1i(prev);
-  to.add_data1i(vertex);
+  size_t offset = vertices->get_num_rows();
+  vertices->set_num_rows(offset + 2);
+
+  PT(GeomVertexArrayDataHandle) handle = vertices->modify_handle();
+  unsigned char *ptr = handle->get_write_pointer();
+  switch (vertices->get_array_format()->get_stride()) {
+  case 1:
+    ((uint8_t *)ptr)[offset] = ((uint8_t *)ptr)[offset - 1];
+    ((uint8_t *)ptr)[offset + 1] = vertex;
+    break;
+  case 2:
+    ((uint16_t *)ptr)[offset] = ((uint16_t *)ptr)[offset - 1];
+    ((uint16_t *)ptr)[offset + 1] = vertex;
+    break;
+  case 4:
+    ((uint32_t *)ptr)[offset] = ((uint32_t *)ptr)[offset - 1];
+    ((uint32_t *)ptr)[offset + 1] = vertex;
+    break;
+  default:
+    nassert_raise("unsupported index type");
+    break;
+  }
 }
 
 /**

+ 8 - 8
panda/src/gobj/geomVertexReader.I

@@ -29,9 +29,9 @@ GeomVertexReader(Thread *current_thread) :
  * object.
  */
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexData *vertex_data,
+GeomVertexReader(CPT(GeomVertexData) vertex_data,
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -43,10 +43,10 @@ GeomVertexReader(const GeomVertexData *vertex_data,
  * data type.
  */
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexData *vertex_data,
+GeomVertexReader(CPT(GeomVertexData) vertex_data,
                  CPT_InternalName name,
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -58,9 +58,9 @@ GeomVertexReader(const GeomVertexData *vertex_data,
  * only.
  */
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexArrayData *array_data,
+GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -71,9 +71,9 @@ GeomVertexReader(const GeomVertexArrayData *array_data,
  * only.
  */
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexArrayData *array_data, int column,
+GeomVertexReader(CPT(GeomVertexArrayData) array_data, int column,
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
 {
   initialize();

+ 4 - 4
panda/src/gobj/geomVertexReader.h

@@ -47,14 +47,14 @@
 class EXPCL_PANDA_GOBJ GeomVertexReader : public GeomEnums {
 PUBLISHED:
   INLINE GeomVertexReader(Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexData *vertex_data,
+  INLINE GeomVertexReader(CPT(GeomVertexData) vertex_data,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexData *vertex_data,
+  INLINE GeomVertexReader(CPT(GeomVertexData) vertex_data,
                           CPT_InternalName name,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexArrayData *array_data,
+  INLINE GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexArrayData *array_data,
+  INLINE GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                           int column,
                           Thread *current_thread = Thread::get_current_thread());
 

+ 8 - 8
panda/src/gobj/geomVertexWriter.I

@@ -29,8 +29,8 @@ GeomVertexWriter(Thread *current_thread) :
  * object.
  */
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexData *vertex_data, Thread *current_thread) :
-  _vertex_data(vertex_data),
+GeomVertexWriter(PT(GeomVertexData) vertex_data, Thread *current_thread) :
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -42,9 +42,9 @@ GeomVertexWriter(GeomVertexData *vertex_data, Thread *current_thread) :
  * data type.
  */
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexData *vertex_data, CPT_InternalName name,
+GeomVertexWriter(PT(GeomVertexData) vertex_data, CPT_InternalName name,
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -56,9 +56,9 @@ GeomVertexWriter(GeomVertexData *vertex_data, CPT_InternalName name,
  * only.
  */
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexArrayData *array_data,
+GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
 {
   initialize();
@@ -69,9 +69,9 @@ GeomVertexWriter(GeomVertexArrayData *array_data,
  * only.
  */
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexArrayData *array_data, int column,
+GeomVertexWriter(PT(GeomVertexArrayData) array_data, int column,
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
 {
   initialize();

+ 4 - 4
panda/src/gobj/geomVertexWriter.h

@@ -55,14 +55,14 @@
 class EXPCL_PANDA_GOBJ GeomVertexWriter : public GeomEnums {
 PUBLISHED:
   INLINE GeomVertexWriter(Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexData *vertex_data,
+  INLINE GeomVertexWriter(PT(GeomVertexData) vertex_data,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexData *vertex_data,
+  INLINE GeomVertexWriter(PT(GeomVertexData) vertex_data,
                           CPT_InternalName name,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexArrayData *array_data,
+  INLINE GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexArrayData *array_data,
+  INLINE GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                           int column,
                           Thread *current_thread = Thread::get_current_thread());
 

+ 9 - 0
panda/src/gobj/shader.h

@@ -227,6 +227,15 @@ public:
 
     STO_stage_i,
     STO_light_i_shadow_map,
+
+    STO_ff_stage_i,
+    STO_stage_modulate_i,
+    STO_stage_add_i,
+    STO_stage_normal_i,
+    STO_stage_height_i,
+    STO_stage_selector_i,
+    STO_stage_gloss_i,
+    STO_stage_emission_i,
   };
 
   enum ShaderArgClass {

+ 3 - 0
panda/src/gobj/textureStage.cxx

@@ -517,6 +517,9 @@ operator << (ostream &out, TextureStage::Mode mode) {
 
   case TextureStage::M_normal_gloss:
     return out << "normal_gloss";
+
+  case TextureStage::M_emission:
+    return out << "emission";
   }
 
   return out << "**invalid Mode(" << (int)mode << ")**";

+ 2 - 0
panda/src/gobj/textureStage.h

@@ -63,6 +63,8 @@ PUBLISHED:
     M_height,       // Rarely used: normal_height  is more efficient.
     M_selector,
     M_normal_gloss,
+
+    M_emission,
   };
 
   enum CombineMode {

+ 3 - 3
panda/src/linmath/lvecBase2_ext_src.I

@@ -221,7 +221,7 @@ __round__(PyObject *self) const {
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);
@@ -241,7 +241,7 @@ __floor__(PyObject *self) const {
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
 
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);
@@ -260,7 +260,7 @@ __ceil__(PyObject *self) const {
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);

+ 3 - 3
panda/src/linmath/lvecBase3_ext_src.I

@@ -224,7 +224,7 @@ __round__(PyObject *self) const {
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);
@@ -244,7 +244,7 @@ __floor__(PyObject *self) const {
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);
@@ -264,7 +264,7 @@ __ceil__(PyObject *self) const {
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);

Some files were not shown because too many files changed in this diff