Browse Source

Merge branch 'master' into shaderpipeline

rdb 3 years ago
parent
commit
9fc3b58a9d
100 changed files with 1990 additions and 586 deletions
  1. 6 6
      .github/workflows/ci.yml
  2. 4 4
      README.md
  3. 12 0
      cmake/modules/FindGTK3.cmake
  4. 9 1
      direct/src/dist/FreezeTool.py
  5. 44 33
      direct/src/dist/commands.py
  6. 2 2
      direct/src/filter/FilterManager.py
  7. 1 1
      direct/src/showbase/JobManager.py
  8. 1 107
      direct/src/showbase/PythonUtil.py
  9. 3 1
      direct/src/showbase/ShowBase.py
  10. 17 10
      direct/src/task/Task.py
  11. 1 1
      doc/INSTALL
  12. 60 0
      doc/ReleaseNotes
  13. 42 5
      dtool/Config.cmake
  14. 8 7
      dtool/Package.cmake
  15. 5 1
      dtool/dtool_config.h.in
  16. 1 1
      dtool/src/dtoolbase/CMakeLists.txt
  17. 1 1
      dtool/src/dtoolbase/deletedBufferChain.I
  18. 13 11
      dtool/src/dtoolbase/deletedBufferChain.cxx
  19. 3 2
      dtool/src/dtoolbase/deletedBufferChain.h
  20. 11 1
      dtool/src/dtoolbase/dtoolbase.h
  21. 0 5
      dtool/src/dtoolbase/dtoolbase_cc.h
  22. 12 0
      dtool/src/dtoolbase/memoryHook.cxx
  23. 0 2
      dtool/src/dtoolbase/mutexSpinlockImpl.h
  24. 267 0
      dtool/src/dtoolbase/patomic.I
  25. 108 0
      dtool/src/dtoolbase/patomic.h
  26. 14 16
      dtool/src/dtoolbase/typeHandle.cxx
  27. 1 2
      dtool/src/dtoolbase/typeRegistryNode.cxx
  28. 2 1
      dtool/src/dtoolbase/typeRegistryNode.h
  29. 2 5
      dtool/src/prc/notify.cxx
  30. 7 2
      makepanda/installpanda.py
  31. 13 16
      makepanda/makepackage.py
  32. 37 14
      makepanda/makepanda.py
  33. 11 8
      makepanda/makepandacore.py
  34. 6 1
      makepanda/makewheel.py
  35. 6 1
      makepanda/test_wheel.py
  36. BIN
      models/plugin_images/auth_click.png
  37. BIN
      models/plugin_images/auth_ready.png
  38. BIN
      models/plugin_images/auth_rollover.png
  39. BIN
      models/plugin_images/download.png
  40. BIN
      models/plugin_images/failed.png
  41. BIN
      models/plugin_images/installer.bmp
  42. BIN
      models/plugin_images/panda3d.icns
  43. BIN
      models/plugin_images/play_click.png
  44. BIN
      models/plugin_images/play_ready.png
  45. BIN
      models/plugin_images/play_rollover.png
  46. 130 28
      panda/src/androiddisplay/androidGraphicsWindow.cxx
  47. 1 0
      panda/src/androiddisplay/androidGraphicsWindow.h
  48. 1 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  49. 1 1
      panda/src/device/evdevInputDevice.cxx
  50. 11 12
      panda/src/egg2pg/eggRenderState.cxx
  51. 42 0
      panda/src/egg2pg/eggSaver.cxx
  52. 1 0
      panda/src/event/asyncFuture.cxx
  53. 49 32
      panda/src/express/referenceCount.I
  54. 11 8
      panda/src/express/referenceCount.cxx
  55. 3 2
      panda/src/express/referenceCount.h
  56. 7 0
      panda/src/express/weakPointerToBase.I
  57. 3 3
      panda/src/express/weakReferenceList.I
  58. 2 2
      panda/src/express/weakReferenceList.cxx
  59. 3 2
      panda/src/express/weakReferenceList.h
  60. 4 0
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  61. 8 0
      panda/src/gobj/geomPrimitive.I
  62. 19 0
      panda/src/gobj/geomPrimitive.cxx
  63. 2 0
      panda/src/gobj/geomPrimitive.h
  64. 18 0
      panda/src/gobj/texture.cxx
  65. 2 2
      panda/src/gobj/texturePeeker.cxx
  66. 1 1
      panda/src/pgraph/cacheStats.I
  67. 3 2
      panda/src/pgraph/cacheStats.cxx
  68. 2 1
      panda/src/pgraph/cacheStats.h
  69. 2 0
      panda/src/pgraph/lightAttrib.cxx
  70. 1 1
      panda/src/pgraph/pandaNode.cxx
  71. 4 0
      panda/src/pgraph/pythonLoaderFileType.cxx
  72. 0 2
      panda/src/pgraph/renderState.cxx
  73. 36 41
      panda/src/pgraphnodes/sceneGraphAnalyzer.cxx
  74. 2 5
      panda/src/pgui/pgScrollFrame.h
  75. 2 5
      panda/src/physics/physicalNode.cxx
  76. 0 16
      panda/src/pipeline/conditionVarDummyImpl.I
  77. 17 0
      panda/src/pipeline/conditionVarDummyImpl.cxx
  78. 2 3
      panda/src/pipeline/conditionVarDummyImpl.h
  79. 2 1
      panda/src/pipeline/thread.h
  80. 2 20
      panda/src/pipeline/threadPosixImpl.I
  81. 28 27
      panda/src/pipeline/threadPosixImpl.cxx
  82. 3 4
      panda/src/pipeline/threadPosixImpl.h
  83. 0 24
      panda/src/pipeline/threadWin32Impl.I
  84. 44 32
      panda/src/pipeline/threadWin32Impl.cxx
  85. 2 6
      panda/src/pipeline/threadWin32Impl.h
  86. 50 4
      panda/src/pnmimage/pnmBrush.cxx
  87. 1 0
      panda/src/pnmimage/pnmBrush.h
  88. 3 2
      panda/src/pnmimage/pnmImage.cxx
  89. 2 1
      panda/src/pnmimage/pnmImage.h
  90. 6 0
      panda/src/pstatclient/pStatClientControlMessage.cxx
  91. 1 0
      panda/src/pstatclient/pStatClientControlMessage.h
  92. 7 1
      panda/src/pstatclient/pStatClientImpl.cxx
  93. 1 0
      panda/src/pstatclient/pStatProperties.cxx
  94. 47 10
      panda/src/windisplay/winGraphicsWindow.cxx
  95. 2 0
      panda/src/x11display/x11GraphicsWindow.cxx
  96. 4 2
      pandatool/src/gtk-stats/CMakeLists.txt
  97. 1 1
      pandatool/src/gtk-stats/gtkStats.cxx
  98. 32 14
      pandatool/src/gtk-stats/gtkStatsChartMenu.cxx
  99. 551 0
      pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx
  100. 81 0
      pandatool/src/gtk-stats/gtkStatsFlameGraph.h

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

@@ -347,16 +347,16 @@ jobs:
       shell: powershell
       shell: powershell
       run: |
       run: |
         $wc = New-Object System.Net.WebClient
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.10/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.11/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz
-        tar -xf panda3d-1.10.10-tools-mac.tar.gz
-        mv panda3d-1.10.10/thirdparty thirdparty
-        rmdir panda3d-1.10.10
+        curl -O https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-mac.tar.gz
+        tar -xf panda3d-1.10.11-tools-mac.tar.gz
+        mv panda3d-1.10.11/thirdparty thirdparty
+        rmdir panda3d-1.10.11
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
     - name: Set up Python 3.9
     - name: Set up Python 3.9
       uses: actions/setup-python@v2
       uses: actions/setup-python@v2

+ 4 - 4
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-10/).
+[this page](https://www.panda3d.org/download/sdk-1-10-11/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
 the following command:
 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
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win32.zip
 
 
 After acquiring these dependencies, you can build Panda3D from the command
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
 prompt using the following command.  Change the `--msvc-version` option based
@@ -136,7 +136,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:

+ 12 - 0
cmake/modules/FindGTK3.cmake

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

+ 9 - 1
direct/src/dist/FreezeTool.py

@@ -91,6 +91,8 @@ ignoreImports = {
 
 
     'toml.encoder': ['numpy'],
     'toml.encoder': ['numpy'],
     'py._builtin': ['__builtin__'],
     'py._builtin': ['__builtin__'],
+
+    'site': ['android_log'],
 }
 }
 
 
 if sys.version_info >= (3, 8):
 if sys.version_info >= (3, 8):
@@ -2406,7 +2408,13 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                     return None
                     return None
 
 
                 try:
                 try:
-                    fp = zip.open(fn.replace(os.path.sep, '/'), 'r')
+                    zip_fn = fn.replace(os.path.sep, '/')
+                    if zip_fn.startswith('deploy_libs/_tkinter.'):
+                        # If we have a tkinter wheel on the path, ignore the
+                        # _tkinter extension in deploy-libs.
+                        if any(entry.endswith(".whl") and os.path.basename(entry).startswith("tkinter-") for entry in self.path):
+                            return None
+                    fp = zip.open(zip_fn, 'r')
                 except KeyError:
                 except KeyError:
                     return None
                     return None
 
 

+ 44 - 33
direct/src/dist/commands.py

@@ -96,6 +96,7 @@ PACKAGE_DATA_DIRS = {
     ],
     ],
     'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
     'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
     'certifi': [('certifi/cacert.pem', '', {})],
     'certifi': [('certifi/cacert.pem', '', {})],
+    '_tkinter_ext': [('_tkinter_ext/tcl/**', 'tcl', {})],
 }
 }
 
 
 # Some dependencies have extra directories that need to be scanned for DLLs.
 # Some dependencies have extra directories that need to be scanned for DLLs.
@@ -140,21 +141,6 @@ def get_data(path):
 
 
 FrozenImporter.find_spec = find_spec
 FrozenImporter.find_spec = find_spec
 FrozenImporter.get_data = get_data
 FrozenImporter.get_data = get_data
-
-# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
-import os
-tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
-if os.path.isdir(tcl_dir):
-    for dir in os.listdir(tcl_dir):
-        sub_dir = os.path.join(tcl_dir, dir)
-        if os.path.isdir(sub_dir):
-            if dir.startswith('tcl'):
-                os.environ['TCL_LIBRARY'] = sub_dir
-            if dir.startswith('tk'):
-                os.environ['TK_LIBRARY'] = sub_dir
-            if dir.startswith('tix'):
-                os.environ['TIX_LIBRARY'] = sub_dir
-del os
 """
 """
 
 
 SITE_PY_ANDROID = """
 SITE_PY_ANDROID = """
@@ -277,7 +263,7 @@ class build_apps(setuptools.Command):
         self.exclude_modules = {}
         self.exclude_modules = {}
         self.icons = {}
         self.icons = {}
         self.platforms = [
         self.platforms = [
-            'manylinux2010_x86_64',
+            'manylinux2014_x86_64',
             'macosx_10_9_x86_64',
             'macosx_10_9_x86_64',
             'win_amd64',
             'win_amd64',
         ]
         ]
@@ -595,10 +581,15 @@ class build_apps(setuptools.Command):
             '-d', whldir,
             '-d', whldir,
             '-r', self.requirements_path,
             '-r', self.requirements_path,
             '--only-binary', ':all:',
             '--only-binary', ':all:',
-            '--platform', platform,
             '--abi', abi_tag,
             '--abi', abi_tag,
+            '--platform', platform,
         ]
         ]
 
 
+        if platform.startswith('linux_'):
+            # Also accept manylinux.
+            arch = platform[6:]
+            pip_args += ['--platform', 'manylinux2014_' + arch]
+
         if self.use_optimized_wheels:
         if self.use_optimized_wheels:
             pip_args += [
             pip_args += [
                 '--extra-index-url', self.optimized_wheel_index
                 '--extra-index-url', self.optimized_wheel_index
@@ -766,6 +757,7 @@ class build_apps(setuptools.Command):
         path = sys.path[:]
         path = sys.path[:]
         p3dwhl = None
         p3dwhl = None
         wheelpaths = []
         wheelpaths = []
+        has_tkinter_wheel = False
 
 
         if use_wheels:
         if use_wheels:
             wheelpaths = self.download_wheels(platform)
             wheelpaths = self.download_wheels(platform)
@@ -775,6 +767,8 @@ class build_apps(setuptools.Command):
                     p3dwhlfn = whl
                     p3dwhlfn = whl
                     p3dwhl = self._get_zip_file(p3dwhlfn)
                     p3dwhl = self._get_zip_file(p3dwhlfn)
                     break
                     break
+                elif os.path.basename(whl).startswith('tkinter-'):
+                    has_tkinter_wheel = True
             else:
             else:
                 raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform))
                 raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform))
 
 
@@ -787,6 +781,11 @@ class build_apps(setuptools.Command):
                         distutils.log.WARN
                         distutils.log.WARN
                     )
                     )
 
 
+            for whl in wheelpaths:
+                if os.path.basename(whl).startswith('tkinter-'):
+                    has_tkinter_wheel = True
+                    break
+
             #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths}
             #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths}
 
 
             # Add whl files to the path so they are picked up by modulefinder
             # Add whl files to the path so they are picked up by modulefinder
@@ -1026,6 +1025,10 @@ class build_apps(setuptools.Command):
         for appname, scriptname in self.console_apps.items():
         for appname, scriptname in self.console_apps.items():
             create_runtime(platform, appname, scriptname, True)
             create_runtime(platform, appname, scriptname, True)
 
 
+        # Warn if tkinter is used but hasn't been added to requirements.txt
+        if not has_tkinter_wheel and '_tkinter' in freezer_modules:
+            self.warn("Detected use of tkinter, but tkinter is not specified in requirements.txt!")
+
         # Copy extension modules
         # Copy extension modules
         whl_modules = []
         whl_modules = []
         whl_modules_ext = ''
         whl_modules_ext = ''
@@ -1040,6 +1043,11 @@ class build_apps(setuptools.Command):
                 if not any(i.endswith(suffix) for suffix in ext_suffixes):
                 if not any(i.endswith(suffix) for suffix in ext_suffixes):
                     continue
                     continue
 
 
+                if has_tkinter_wheel and i.startswith('deploy_libs/_tkinter.'):
+                    # Ignore this one, we have a separate tkinter package
+                    # nowadays that contains all the dependencies.
+                    continue
+
                 base = os.path.basename(i)
                 base = os.path.basename(i)
                 module, _, ext = base.partition('.')
                 module, _, ext = base.partition('.')
                 whl_modules.append(module)
                 whl_modules.append(module)
@@ -1111,22 +1119,6 @@ class build_apps(setuptools.Command):
             search_path = get_search_path_for(source_path)
             search_path = get_search_path_for(source_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
 
 
-        # Copy over the tcl directory.
-        #TODO: get this to work on non-Windows platforms.
-        if sys.platform == "win32" and platform.startswith('win'):
-            tcl_dir = os.path.join(sys.prefix, 'tcl')
-
-            if os.path.isdir(tcl_dir) and 'tkinter' in freezer_modules:
-                self.announce('Copying Tcl files', distutils.log.INFO)
-                os.makedirs(os.path.join(binary_dir, 'tcl'))
-
-                for dir in os.listdir(tcl_dir):
-                    sub_dir = os.path.join(tcl_dir, dir)
-                    if os.path.isdir(sub_dir):
-                        target_dir = os.path.join(binary_dir, 'tcl', dir)
-                        self.announce('copying {0} -> {1}'.format(sub_dir, target_dir))
-                        shutil.copytree(sub_dir, target_dir)
-
         # Copy classes.dex on Android
         # Copy classes.dex on Android
         if use_wheels and platform.startswith('android'):
         if use_wheels and platform.startswith('android'):
             self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'),
             self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'),
@@ -1151,9 +1143,14 @@ class build_apps(setuptools.Command):
                     target_dir = os.path.join(data_dir, target_dir)
                     target_dir = os.path.join(data_dir, target_dir)
 
 
                     for wf in filenames:
                     for wf in filenames:
+                        if wf.endswith('/'):
+                            # Skip directories.
+                            continue
+
                         if wf.lower().startswith(source_dir.lower() + '/'):
                         if wf.lower().startswith(source_dir.lower() + '/'):
                             if not srcglob.matches(wf.lower()):
                             if not srcglob.matches(wf.lower()):
                                 continue
                                 continue
+
                             wf = wf.replace('/', os.sep)
                             wf = wf.replace('/', os.sep)
                             relpath = wf[len(source_dir) + 1:]
                             relpath = wf[len(source_dir) + 1:]
                             source_path = os.path.join(whl, wf)
                             source_path = os.path.join(whl, wf)
@@ -1597,6 +1594,20 @@ class bdist_apps(setuptools.Command):
         'manylinux1_i686': ['gztar'],
         'manylinux1_i686': ['gztar'],
         'manylinux2010_x86_64': ['gztar'],
         'manylinux2010_x86_64': ['gztar'],
         'manylinux2010_i686': ['gztar'],
         'manylinux2010_i686': ['gztar'],
+        'manylinux2014_x86_64': ['gztar'],
+        'manylinux2014_i686': ['gztar'],
+        'manylinux2014_aarch64': ['gztar'],
+        'manylinux2014_armv7l': ['gztar'],
+        'manylinux2014_ppc64': ['gztar'],
+        'manylinux2014_ppc64le': ['gztar'],
+        'manylinux2014_s390x': ['gztar'],
+        'manylinux_2_24_x86_64': ['gztar'],
+        'manylinux_2_24_i686': ['gztar'],
+        'manylinux_2_24_aarch64': ['gztar'],
+        'manylinux_2_24_armv7l': ['gztar'],
+        'manylinux_2_24_ppc64': ['gztar'],
+        'manylinux_2_24_ppc64le': ['gztar'],
+        'manylinux_2_24_s390x': ['gztar'],
         'android': ['aab'],
         'android': ['aab'],
         # Everything else defaults to ['zip']
         # Everything else defaults to ['zip']
     }
     }

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

@@ -123,8 +123,8 @@ class FilterManager(DirectObject):
             winy = winy // div
             winy = winy // div
 
 
         if mul != 1:
         if mul != 1:
-            winx = winx * mul
-            winy = winy * mul
+            winx = int(round(winx * mul))
+            winy = int(round(winy * mul))
 
 
         return winx,winy
         return winx,winy
 
 

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

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

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

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

+ 3 - 1
direct/src/showbase/ShowBase.py

@@ -19,7 +19,9 @@ Built-in global variables
 Some key variables used in all Panda3D scripts are actually attributes of the
 Some key variables used in all Panda3D scripts are actually attributes of the
 ShowBase instance.  When creating an instance of this class, it will write many
 ShowBase instance.  When creating an instance of this class, it will write many
 of these variables to the built-in scope of the Python interpreter, so that
 of these variables to the built-in scope of the Python interpreter, so that
-they are accessible to any Python module, without the need fors extra imports.
+they are accessible to any Python module, without the need for extra imports.
+For example, the ShowBase instance itself is accessible anywhere through the
+:data:`~builtins.base` variable.
 
 
 While these are handy for prototyping, we do not recommend using them in bigger
 While these are handy for prototyping, we do not recommend using them in bigger
 projects, as it can make the code confusing to read to other Python developers,
 projects, as it can make the code confusing to read to other Python developers,

+ 17 - 10
direct/src/task/Task.py

@@ -127,6 +127,8 @@ class TaskManager:
         self.destroyed = False
         self.destroyed = False
         self.fKeyboardInterrupt = False
         self.fKeyboardInterrupt = False
         self.interruptCount = 0
         self.interruptCount = 0
+        if signal:
+            self.__prevHandler = signal.default_int_handler
 
 
         self._frameProfileQueue = []
         self._frameProfileQueue = []
 
 
@@ -168,7 +170,7 @@ class TaskManager:
         print('*** allowing mid-frame keyboard interrupt.')
         print('*** allowing mid-frame keyboard interrupt.')
         # Restore default interrupt handler
         # Restore default interrupt handler
         if signal:
         if signal:
-            signal.signal(signal.SIGINT, signal.default_int_handler)
+            signal.signal(signal.SIGINT, self.__prevHandler)
         # and invoke it
         # and invoke it
         raise KeyboardInterrupt
         raise KeyboardInterrupt
 
 
@@ -475,25 +477,30 @@ class TaskManager:
         chains that are in sub-threads or that have frame budgets
         chains that are in sub-threads or that have frame budgets
         might execute their tasks differently. """
         might execute their tasks differently. """
 
 
+        startFrameTime = self.globalClock.getRealTime()
+
         # Replace keyboard interrupt handler during task list processing
         # Replace keyboard interrupt handler during task list processing
         # so we catch the keyboard interrupt but don't handle it until
         # so we catch the keyboard interrupt but don't handle it until
         # after task list processing is complete.
         # after task list processing is complete.
         self.fKeyboardInterrupt = 0
         self.fKeyboardInterrupt = 0
         self.interruptCount = 0
         self.interruptCount = 0
+
         if signal:
         if signal:
-            signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
+            self.__prevHandler = signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
 
 
-        startFrameTime = self.globalClock.getRealTime()
+        try:
+            self.mgr.poll()
 
 
-        self.mgr.poll()
+            # This is the spot for an internal yield function
+            nextTaskTime = self.mgr.getNextWakeTime()
+            self.doYield(startFrameTime, nextTaskTime)
 
 
-        # This is the spot for an internal yield function
-        nextTaskTime = self.mgr.getNextWakeTime()
-        self.doYield(startFrameTime, nextTaskTime)
+        finally:
+            # Restore previous interrupt handler
+            if signal:
+                signal.signal(signal.SIGINT, self.__prevHandler)
+                self.__prevHandler = signal.default_int_handler
 
 
-        # Restore default interrupt handler
-        if signal:
-            signal.signal(signal.SIGINT, signal.default_int_handler)
         if self.fKeyboardInterrupt:
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
             raise KeyboardInterrupt
 
 

+ 1 - 1
doc/INSTALL

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

+ 60 - 0
doc/ReleaseNotes

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

+ 42 - 5
dtool/Config.cmake

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

+ 8 - 7
dtool/Package.cmake

@@ -553,16 +553,17 @@ package_option(HarfBuzz
 
 
 package_status(HarfBuzz "HarfBuzz")
 package_status(HarfBuzz "HarfBuzz")
 
 
-# GTK2
+# GTK3
 
 
-set(Freetype_FIND_QUIETLY TRUE) # Fix for builtin FindGTK2
-set(GTK2_GTK_FIND_QUIETLY TRUE) # Fix for builtin FindGTK2
-find_package(GTK2 QUIET COMPONENTS gtk)
-
-package_option(GTK2)
+if(NOT WIN32)
+  find_package(GTK3 QUIET)
+endif()
 
 
-package_status(GTK2 "gtk+-2")
+package_option(GTK3
+  "This is necessary to build the PStats performance analysis tool on platforms
+  other than Windows.")
 
 
+package_status(GTK3 "gtk+-3")
 
 
 #
 #
 # ------------ Physics engines ------------
 # ------------ Physics engines ------------

+ 5 - 1
dtool/dtool_config.h.in

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

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

@@ -92,7 +92,7 @@ add_component_library(p3dtoolbase NOINIT SYMBOL BUILDING_DTOOL_DTOOLBASE
 target_include_directories(p3dtoolbase PUBLIC
 target_include_directories(p3dtoolbase PUBLIC
   $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
   $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
   $<BUILD_INTERFACE:${PANDA_OUTPUT_DIR}/include>)
   $<BUILD_INTERFACE:${PANDA_OUTPUT_DIR}/include>)
-target_link_libraries(p3dtoolbase PKG::EIGEN PKG::THREADS)
+target_link_libraries(p3dtoolbase PKG::EIGEN PKG::THREADS PKG::MIMALLOC)
 target_interrogate(p3dtoolbase ${P3DTOOLBASE_SOURCES} EXTENSIONS ${P3DTOOLBASE_IGATEEXT})
 target_interrogate(p3dtoolbase ${P3DTOOLBASE_SOURCES} EXTENSIONS ${P3DTOOLBASE_IGATEEXT})
 
 
 if(NOT BUILD_METALIBS)
 if(NOT BUILD_METALIBS)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,267 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file patomic.I
+ * @author rdb
+ * @date 2022-01-28
+ */
+
+/**
+ * Value initializer.
+ */
+template<class T>
+constexpr patomic<T>::
+patomic(T desired) noexcept : _value(desired) {
+}
+
+/**
+ * Returns true if this is a lock free type (which it always is).
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+is_lock_free() const noexcept {
+  return true;
+}
+
+/**
+ * Returns the stored value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+load(std::memory_order order) const noexcept {
+  return _value;
+}
+
+/**
+ * Returns the stored value.
+ */
+template<class T>
+ALWAYS_INLINE patomic<T>::
+operator T() const noexcept {
+  return _value;
+}
+
+/**
+ * Changes the stored value.
+ */
+template<class T>
+ALWAYS_INLINE void patomic<T>::
+store(T desired, std::memory_order order) noexcept {
+  _value = desired;
+}
+
+/**
+ * Changes the stored value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator=(T desired) noexcept {
+  _value = desired;
+}
+
+/**
+ * Changes the stored value, returning the previous value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+exchange(T desired, std::memory_order) noexcept {
+  T current = _value;
+  _value = desired;
+  return current;
+}
+
+/**
+ * Sets the desired value if the current value is as the first argument.
+ * If it is not, the current value is written to expected.
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+compare_exchange_weak(T &expected, T desired,
+                      std::memory_order, std::memory_order) noexcept {
+  T current = _value;
+  if (_value == expected) {
+    _value = desired;
+    return true;
+  } else {
+    expected = current;
+    return false;
+  }
+}
+
+/**
+ * Sets the desired value if the current value is as the first argument.
+ * If it is not, the current value is written to expected.
+ */
+template<class T>
+ALWAYS_INLINE bool patomic<T>::
+compare_exchange_strong(T &expected, T desired,
+                        std::memory_order, std::memory_order) noexcept {
+  T current = _value;
+  if (_value == expected) {
+    _value = desired;
+    return true;
+  } else {
+    expected = current;
+    return false;
+  }
+}
+
+/**
+ * Adds to the stored value, returns the old value.
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_add(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value += arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_sub(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value -= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_and(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value &= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_or(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value |= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+fetch_xor(T arg, std::memory_order) noexcept {
+  T old = _value;
+  _value ^= arg;
+  return old;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ++(int) noexcept {
+  return _value++;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator --(int) noexcept {
+  return _value--;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ++() noexcept {
+  return ++_value;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator --() noexcept {
+  return --_value;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator +=(T arg) noexcept {
+  return _value += arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator -=(T arg) noexcept {
+  return _value -= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator &=(T arg) noexcept {
+  return _value &= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator |=(T arg) noexcept {
+  return _value |= arg;
+}
+
+/**
+ *
+ */
+template<class T>
+ALWAYS_INLINE T patomic<T>::
+operator ^=(T arg) noexcept {
+  return _value ^= arg;
+}
+
+
+/**
+ * Sets the flag to true and returns the previous value.
+ */
+ALWAYS_INLINE bool patomic_flag::
+test_and_set(std::memory_order order) noexcept {
+  bool value = __internal_flag;
+  __internal_flag = true;
+  return value;
+}
+
+/**
+ * Sets the flag to false.
+ */
+ALWAYS_INLINE void patomic_flag::
+clear(std::memory_order order) noexcept {
+  __internal_flag = false;
+}

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

@@ -0,0 +1,108 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file patomic.h
+ * @author rdb
+ * @date 2022-01-28
+ */
+
+#ifndef PATOMIC_H
+#define PATOMIC_H
+
+#include "dtoolbase.h"
+#include "selectThreadImpl.h"
+
+#include <atomic>
+
+#if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
+
+/**
+ * Dummy implementation of std::atomic that does not do any atomic operations,
+ * used when compiling without HAVE_THREADS or with SIMPLE_THREADS.
+ */
+template<class T>
+struct patomic {
+  using value_type = T;
+
+  constexpr patomic() noexcept = default;
+  constexpr patomic(T desired) noexcept;
+
+  ALWAYS_INLINE patomic(const patomic &) = delete;
+  ALWAYS_INLINE patomic &operator=(const patomic &) = delete;
+
+  static constexpr bool is_always_lock_free = true;
+  ALWAYS_INLINE bool is_lock_free() const noexcept;
+
+  ALWAYS_INLINE T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
+  ALWAYS_INLINE operator T() const noexcept;
+
+  ALWAYS_INLINE void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T operator=(T desired) noexcept;
+
+  ALWAYS_INLINE T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE bool compare_exchange_weak(T &expected, T desired,
+                                           std::memory_order success = std::memory_order_seq_cst,
+                                           std::memory_order failure = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE bool compare_exchange_strong(T &expected, T desired,
+                                             std::memory_order success = std::memory_order_seq_cst,
+                                             std::memory_order failure = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_and(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_or(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE T fetch_xor(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  ALWAYS_INLINE T operator ++(int) noexcept;
+  ALWAYS_INLINE T operator --(int) noexcept;
+  ALWAYS_INLINE T operator ++() noexcept;
+  ALWAYS_INLINE T operator --() noexcept;
+  ALWAYS_INLINE T operator +=(T arg) noexcept;
+  ALWAYS_INLINE T operator -=(T arg) noexcept;
+  ALWAYS_INLINE T operator &=(T arg) noexcept;
+  ALWAYS_INLINE T operator |=(T arg) noexcept;
+  ALWAYS_INLINE T operator ^=(T arg) noexcept;
+
+private:
+  T _value;
+};
+
+/**
+ * Dummy implementation of std::atomic_flag that does not do any atomic
+ * operations.
+ */
+struct EXPCL_DTOOL_DTOOLBASE patomic_flag {
+  constexpr patomic_flag() noexcept = default;
+
+  patomic_flag(const patomic_flag &) = delete;
+  patomic_flag &operator=(const patomic_flag &) = delete;
+
+  ALWAYS_INLINE bool test_and_set(std::memory_order order = std::memory_order_seq_cst) noexcept;
+  ALWAYS_INLINE void clear(std::memory_order order = std::memory_order_seq_cst) noexcept;
+
+  bool __internal_flag = false;
+};
+
+#define patomic_thread_fence(order) (std::atomic_signal_fence((order)))
+
+#include "patomic.I"
+
+#else
+
+// We're using real threading, so use the real implementation.
+template<class T>
+using patomic = std::atomic<T>;
+
+typedef std::atomic_flag patomic_flag;
+
+#define patomic_thread_fence(order) (std::atomic_thread_fence((order)))
+
+#endif
+
+#endif

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

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

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

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

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

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

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

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

+ 7 - 2
makepanda/installpanda.py

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

+ 13 - 16
makepanda/makepackage.py

@@ -6,7 +6,6 @@ import shutil
 import glob
 import glob
 import re
 import re
 import subprocess
 import subprocess
-import sysconfig
 from makepandacore import *
 from makepandacore import *
 from installpanda import *
 from installpanda import *
 
 
@@ -219,18 +218,10 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
                        python_versions=[], **kwargs):
                        python_versions=[], **kwargs):
     outputdir = GetOutputDir()
     outputdir = GetOutputDir()
 
 
-    # We pack the default Python 3 version that ships with Ubuntu.
-    python3_ver = None
+    # Only pack the versions of Python included with this Ubuntu version.
     install_python_versions = []
     install_python_versions = []
-
-    # What's the system version of Python 3?
-    oscmd('python3 -V > "%s/tmp/python3_version.txt"' % (outputdir))
-    sys_python3_ver = '.'.join(ReadFile(outputdir + "/tmp/python3_version.txt").strip().split(' ')[1].split('.')[:2])
-
-    # Check that we built with support for it.
     for version_info in python_versions:
     for version_info in python_versions:
-        if version_info["version"] == sys_python3_ver:
-            python3_ver = sys_python3_ver
+        if os.path.isdir("/usr/lib/python" + version_info["version"]):
             install_python_versions.append(version_info)
             install_python_versions.append(version_info)
 
 
     major_version = '.'.join(version.split('.')[:2])
     major_version = '.'.join(version.split('.')[:2])
@@ -317,9 +308,13 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
         recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
         recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
         provides = "panda3d"
         provides = "panda3d"
 
 
-        if python3_ver:
-            depends += ", python%s" % (python3_ver)
-            recommends += ", python-pmw, python3-tk (>= %s)" % (python3_ver)
+        # Require at least one of the Python versions we built for.
+        if install_python_versions:
+            depends += ", " + " | ".join("python" + version_info["version"] for version_info in install_python_versions)
+
+            # But recommend the system version of Python 3.
+            recommends += ", python3"
+            recommends += ", python3-tk"
             provides += ", python3-panda3d"
             provides += ", python3-panda3d"
 
 
         # Write back the dependencies, and delete the dummy set-up.
         # Write back the dependencies, and delete the dummy set-up.
@@ -333,7 +328,7 @@ def MakeInstallerLinux(version, debversion=None, rpmversion=None, rpmrelease=1,
         oscmd("chmod -R 755 targetroot/DEBIAN")
         oscmd("chmod -R 755 targetroot/DEBIAN")
         oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums")
         oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums")
         oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols")
         oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols")
-        oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
+        oscmd("fakeroot dpkg-deb -Zxz -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
 
 
     elif rpmbuild_present:
     elif rpmbuild_present:
         # Invoke installpanda.py to install it into a temporary dir
         # Invoke installpanda.py to install it into a temporary dir
@@ -910,7 +905,9 @@ def MakeInstallerAndroid(version, **kwargs):
                     shutil.copy(os.path.join(source_dir, base), target)
                     shutil.copy(os.path.join(source_dir, base), target)
 
 
     # Copy the Python standard library to the .apk as well.
     # Copy the Python standard library to the .apk as well.
-    stdlib_source = sysconfig.get_path("stdlib")
+    # DO NOT CHANGE TO sysconfig - see #1230
+    from distutils.sysconfig import get_python_lib
+    stdlib_source = get_python_lib(False, True)
     stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
     stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
     copy_python_tree(stdlib_source, stdlib_target)
     copy_python_tree(stdlib_source, stdlib_target)
 
 

+ 37 - 14
makepanda/makepanda.py

@@ -21,7 +21,6 @@ try:
     import threading
     import threading
     import signal
     import signal
     import shutil
     import shutil
-    import sysconfig
     import plistlib
     import plistlib
     import queue
     import queue
 except KeyboardInterrupt:
 except KeyboardInterrupt:
@@ -94,7 +93,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "VRPN", "OPENSSL",                                   # Transport
   "VRPN", "OPENSSL",                                   # Transport
   "FFTW",                                              # Algorithm helpers
   "FFTW",                                              # Algorithm helpers
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
-  "GTK2",                                              # GTK2 is used for PStats on Unix
+  "GTK3",                                              # GTK3 is used for PStats on Unix
   "MFC", "WX", "FLTK",                                 # Used for web plug-in only
   "MFC", "WX", "FLTK",                                 # Used for web plug-in only
   "COCOA",                                             # macOS toolkits
   "COCOA",                                             # macOS toolkits
   "X11",                                               # Unix platform support
   "X11",                                               # Unix platform support
@@ -104,6 +103,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "PANDAPARTICLESYSTEM",                               # Built in particle system
   "PANDAPARTICLESYSTEM",                               # Built in particle system
   "CONTRIB",                                           # Experimental
   "CONTRIB",                                           # Experimental
   "SSE2", "NEON",                                      # Compiler features
   "SSE2", "NEON",                                      # Compiler features
+  "MIMALLOC",                                          # Memory allocators
 ])
 ])
 
 
 CheckPandaSourceTree()
 CheckPandaSourceTree()
@@ -429,6 +429,8 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile
     # This is manylinux1.  A bit of a sloppy check, though.
     # This is manylinux1.  A bit of a sloppy check, though.
     if GetTargetArch() in ('x86_64', 'amd64'):
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux1-x86_64'
         PLATFORM = 'manylinux1-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux1-aarch64'
     else:
     else:
         PLATFORM = 'manylinux1-i686'
         PLATFORM = 'manylinux1-i686'
 
 
@@ -436,6 +438,8 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.12.so") or os.path.isfil
     # Same sloppy check for manylinux2010.
     # Same sloppy check for manylinux2010.
     if GetTargetArch() in ('x86_64', 'amd64'):
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2010-x86_64'
         PLATFORM = 'manylinux2010-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux2010-aarch64'
     else:
     else:
         PLATFORM = 'manylinux2010-i686'
         PLATFORM = 'manylinux2010-i686'
 
 
@@ -443,13 +447,17 @@ elif target == 'linux' and (os.path.isfile("/lib/libc-2.17.so") or os.path.isfil
     # Same sloppy check for manylinux2014.
     # Same sloppy check for manylinux2014.
     if GetTargetArch() in ('x86_64', 'amd64'):
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2014-x86_64'
         PLATFORM = 'manylinux2014-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux2014-aarch64'
     else:
     else:
         PLATFORM = 'manylinux2014-i686'
         PLATFORM = 'manylinux2014-i686'
 
 
-elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64/libc-2.24.so")) and os.path.isdir("/opt/python"):
+elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so")) and os.path.isdir("/opt/python"):
     # Same sloppy check for manylinux_2_24.
     # Same sloppy check for manylinux_2_24.
     if GetTargetArch() in ('x86_64', 'amd64'):
     if GetTargetArch() in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux_2_24-x86_64'
         PLATFORM = 'manylinux_2_24-x86_64'
+    elif GetTargetArch() in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux_2_24-aarch64'
     else:
     else:
         PLATFORM = 'manylinux_2_24-i686'
         PLATFORM = 'manylinux_2_24-i686'
 
 
@@ -639,6 +647,7 @@ if (COMPILER == "MSVC"):
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "quartz.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "quartz.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbc32.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbc32.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbccp32.lib")
     if (PkgSkip("DIRECTCAM")==0): LibName("DIRECTCAM", "odbccp32.lib")
+    if (PkgSkip("MIMALLOC")==0): LibName("MIMALLOC", GetThirdpartyDir() + "mimalloc/lib/mimalloc-static.lib")
     if (PkgSkip("OPENSSL")==0):
     if (PkgSkip("OPENSSL")==0):
         if os.path.isfile(GetThirdpartyDir() + "openssl/lib/libpandassl.lib"):
         if os.path.isfile(GetThirdpartyDir() + "openssl/lib/libpandassl.lib"):
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libpandassl.lib")
             LibName("OPENSSL", GetThirdpartyDir() + "openssl/lib/libpandassl.lib")
@@ -878,6 +887,7 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("SPIRV-TOOLS", "",        ("SPIRV-Tools", "SPIRV-Tools-opt"), "spirv-tools/optimizer.hpp")
     SmartPkgEnable("SPIRV-TOOLS", "",        ("SPIRV-Tools", "SPIRV-Tools-opt"), "spirv-tools/optimizer.hpp")
     SmartPkgEnable("SPIRV-CROSS-GLSL", "",   ("spirv-cross-core", "spirv-cross-glsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
     SmartPkgEnable("SPIRV-CROSS-GLSL", "",   ("spirv-cross-core", "spirv-cross-glsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
     SmartPkgEnable("SPIRV-CROSS-HLSL", "",   ("spirv-cross-core", "spirv-cross-hlsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
     SmartPkgEnable("SPIRV-CROSS-HLSL", "",   ("spirv-cross-core", "spirv-cross-hlsl"), "spirv_cross/spirv_cross.hpp", thirdparty_dir="spirv-cross")
+    SmartPkgEnable("MIMALLOC",  "",          ("mimalloc"), "mimalloc.h")
 
 
     # Copy freetype libraries to be specified after harfbuzz libraries as well,
     # Copy freetype libraries to be specified after harfbuzz libraries as well,
     # because there's a circular dependency between the two libraries.
     # because there's a circular dependency between the two libraries.
@@ -947,6 +957,9 @@ if (COMPILER=="GCC"):
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
 
 
+        if not PkgSkip("MIMALLOC"):
+            LibName("MIMALLOC", "-Wl,--exclude-libs,libmimalloc.a")
+
         LibName("GLSLANG", "-Wl,--exclude-libs,libglslang.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libglslang.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libSPIRV.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libSPIRV.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libOSDependent.a")
         LibName("GLSLANG", "-Wl,--exclude-libs,libOSDependent.a")
@@ -1001,7 +1014,7 @@ if (COMPILER=="GCC"):
 
 
     SmartPkgEnable("OPENSSL",   "openssl",   ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h"))
     SmartPkgEnable("OPENSSL",   "openssl",   ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h"))
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
-    SmartPkgEnable("GTK2",      "gtk+-2.0")
+    SmartPkgEnable("GTK3",      "gtk+-3.0")
 
 
     if not PkgSkip("OPENSSL") and GetTarget() != "darwin":
     if not PkgSkip("OPENSSL") and GetTarget() != "darwin":
         LibName("OPENSSL", "-Wl,--exclude-libs,libssl.a")
         LibName("OPENSSL", "-Wl,--exclude-libs,libssl.a")
@@ -1016,11 +1029,6 @@ if (COMPILER=="GCC"):
     if GetHost() != "darwin":
     if GetHost() != "darwin":
         # Workaround for an issue where pkg-config does not include this path
         # Workaround for an issue where pkg-config does not include this path
         if GetTargetArch() in ("x86_64", "amd64"):
         if GetTargetArch() in ("x86_64", "amd64"):
-            if (os.path.isdir("/usr/lib64/glib-2.0/include")):
-                IncDirectory("GTK2", "/usr/lib64/glib-2.0/include")
-            if (os.path.isdir("/usr/lib64/gtk-2.0/include")):
-                IncDirectory("GTK2", "/usr/lib64/gtk-2.0/include")
-
             if not PkgSkip("X11"):
             if not PkgSkip("X11"):
                 if (os.path.isdir("/usr/X11R6/lib64")):
                 if (os.path.isdir("/usr/X11R6/lib64")):
                     LibDirectory("ALWAYS", "/usr/X11R6/lib64")
                     LibDirectory("ALWAYS", "/usr/X11R6/lib64")
@@ -2331,6 +2339,7 @@ DTOOL_CONFIG=[
     ("REPORT_OPENSSL_ERRORS",          '1',                      '1'),
     ("REPORT_OPENSSL_ERRORS",          '1',                      '1'),
     ("USE_PANDAFILESTREAM",            '1',                      '1'),
     ("USE_PANDAFILESTREAM",            '1',                      '1'),
     ("USE_DELETED_CHAIN",              '1',                      '1'),
     ("USE_DELETED_CHAIN",              '1',                      '1'),
+    ("HAVE_MIMALLOC",                  'UNDEF',                  'UNDEF'),
     ("HAVE_WGL",                       '1',                      'UNDEF'),
     ("HAVE_WGL",                       '1',                      'UNDEF'),
     ("HAVE_DX9",                       'UNDEF',                  'UNDEF'),
     ("HAVE_DX9",                       'UNDEF',                  'UNDEF'),
     ("HAVE_THREADS",                   '1',                      '1'),
     ("HAVE_THREADS",                   '1',                      '1'),
@@ -2473,6 +2482,20 @@ def WriteConfigSettings():
 
 
     dtool_config["HAVE_NET"] = '1'
     dtool_config["HAVE_NET"] = '1'
 
 
+    if GetTarget() == 'windows':
+        if not PkgSkip("MIMALLOC"):
+            # This is faster than both DeletedBufferChain and malloc,
+            # especially in the multi-threaded case.
+            dtool_config["USE_MEMORY_MIMALLOC"] = '1'
+            dtool_config["USE_DELETED_CHAIN"] = 'UNDEF'
+        else:
+            # If we don't have mimalloc, use DeletedBufferChain as fallback,
+            # which is still more efficient than malloc.
+            dtool_config["USE_DELETED_CHAIN"] = '1'
+    else:
+        # On other systems, the default malloc seems to be fine.
+        dtool_config["USE_DELETED_CHAIN"] = 'UNDEF'
+
     if GetTarget() not in ("linux", "android"):
     if GetTarget() not in ("linux", "android"):
         dtool_config["HAVE_PROC_SELF_EXE"] = 'UNDEF'
         dtool_config["HAVE_PROC_SELF_EXE"] = 'UNDEF'
         dtool_config["HAVE_PROC_SELF_MAPS"] = 'UNDEF'
         dtool_config["HAVE_PROC_SELF_MAPS"] = 'UNDEF'
@@ -3369,7 +3392,7 @@ if GetTarget() == 'windows':
 # DIRECTORY: dtool/src/dtoolbase/
 # DIRECTORY: dtool/src/dtoolbase/
 #
 #
 
 
-OPTS=['DIR:dtool/src/dtoolbase', 'BUILDING:DTOOL']
+OPTS=['DIR:dtool/src/dtoolbase', 'BUILDING:DTOOL', 'MIMALLOC']
 TargetAdd('p3dtoolbase_composite1.obj', opts=OPTS, input='p3dtoolbase_composite1.cxx')
 TargetAdd('p3dtoolbase_composite1.obj', opts=OPTS, input='p3dtoolbase_composite1.cxx')
 TargetAdd('p3dtoolbase_composite2.obj', opts=OPTS, input='p3dtoolbase_composite2.cxx')
 TargetAdd('p3dtoolbase_composite2.obj', opts=OPTS, input='p3dtoolbase_composite2.cxx')
 TargetAdd('p3dtoolbase_lookup3.obj',    opts=OPTS, input='lookup3.c')
 TargetAdd('p3dtoolbase_lookup3.obj',    opts=OPTS, input='lookup3.c')
@@ -3400,7 +3423,7 @@ TargetAdd('libp3dtool.dll', input='p3dtoolbase_composite1.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_composite2.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_composite2.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_indent.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_indent.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_lookup3.obj')
 TargetAdd('libp3dtool.dll', input='p3dtoolbase_lookup3.obj')
-TargetAdd('libp3dtool.dll', opts=['ADVAPI','WINSHELL','WINKERNEL'])
+TargetAdd('libp3dtool.dll', opts=['ADVAPI','WINSHELL','WINKERNEL','MIMALLOC'])
 
 
 #
 #
 # DIRECTORY: dtool/src/cppparser/
 # DIRECTORY: dtool/src/cppparser/
@@ -5886,19 +5909,19 @@ if not PkgSkip("PANDATOOL"):
 # DIRECTORY: pandatool/src/gtk-stats/
 # DIRECTORY: pandatool/src/gtk-stats/
 #
 #
 
 
-if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK2")):
+if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK3")):
     if GetTarget() == 'windows':
     if GetTarget() == 'windows':
         OPTS=['DIR:pandatool/src/win-stats']
         OPTS=['DIR:pandatool/src/win-stats']
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='winstats_composite1.cxx')
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='winstats_composite1.cxx')
     else:
     else:
-        OPTS=['DIR:pandatool/src/gtk-stats', 'GTK2']
+        OPTS=['DIR:pandatool/src/gtk-stats', 'GTK3']
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='gtkstats_composite1.cxx')
         TargetAdd('pstats_composite1.obj', opts=OPTS, input='gtkstats_composite1.cxx')
     TargetAdd('pstats.exe', input='pstats_composite1.obj')
     TargetAdd('pstats.exe', input='pstats_composite1.obj')
     TargetAdd('pstats.exe', input='libp3pstatserver.lib')
     TargetAdd('pstats.exe', input='libp3pstatserver.lib')
     TargetAdd('pstats.exe', input='libp3progbase.lib')
     TargetAdd('pstats.exe', input='libp3progbase.lib')
     TargetAdd('pstats.exe', input='libp3pandatoolbase.lib')
     TargetAdd('pstats.exe', input='libp3pandatoolbase.lib')
     TargetAdd('pstats.exe', input=COMMON_PANDA_LIBS)
     TargetAdd('pstats.exe', input=COMMON_PANDA_LIBS)
-    TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK2'])
+    TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'GTK3'])
 
 
 #
 #
 # DIRECTORY: pandatool/src/xfileprogs/
 # DIRECTORY: pandatool/src/xfileprogs/

+ 11 - 8
makepanda/makepandacore.py

@@ -6,6 +6,7 @@
 ########################################################################
 ########################################################################
 
 
 import configparser
 import configparser
+from distutils import sysconfig # DO NOT CHANGE to sysconfig - see #1230
 import fnmatch
 import fnmatch
 import getpass
 import getpass
 import glob
 import glob
@@ -17,7 +18,6 @@ import shutil
 import signal
 import signal
 import subprocess
 import subprocess
 import sys
 import sys
-import sysconfig
 import threading
 import threading
 import _thread as thread
 import _thread as thread
 import time
 import time
@@ -717,7 +717,7 @@ def GetTimestamp(path):
     if path in TIMESTAMPCACHE:
     if path in TIMESTAMPCACHE:
         return TIMESTAMPCACHE[path]
         return TIMESTAMPCACHE[path]
     try:
     try:
-        date = os.path.getmtime(path)
+        date = int(os.path.getmtime(path))
     except:
     except:
         date = 0
         date = 0
     TIMESTAMPCACHE[path] = date
     TIMESTAMPCACHE[path] = date
@@ -871,7 +871,7 @@ def JavaGetImports(path):
 ##
 ##
 ########################################################################
 ########################################################################
 
 
-DCACHE_VERSION = 2
+DCACHE_VERSION = 3
 DCACHE_BACKED_UP = False
 DCACHE_BACKED_UP = False
 
 
 def SaveDependencyCache():
 def SaveDependencyCache():
@@ -1640,7 +1640,10 @@ def LocateLibrary(lib, lpath=[], prefer_static=False):
                 return os.path.join(dir, 'lib%s.a' % lib)
                 return os.path.join(dir, 'lib%s.a' % lib)
 
 
     for dir in lpath:
     for dir in lpath:
-        if target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
+        if target == 'windows':
+            if os.path.isfile(os.path.join(dir, lib + '.lib')):
+                return os.path.join(dir, lib + '.lib')
+        elif target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
             return os.path.join(dir, 'lib%s.dylib' % lib)
             return os.path.join(dir, 'lib%s.dylib' % lib)
         elif target != 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.so' % lib)):
         elif target != 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.so' % lib)):
             return os.path.join(dir, 'lib%s.so' % lib)
             return os.path.join(dir, 'lib%s.so' % lib)
@@ -2178,12 +2181,12 @@ def SdkLocatePython(prefer_thirdparty_python=False):
         LibDirectory("PYTHON", py_fwx + "/lib")
         LibDirectory("PYTHON", py_fwx + "/lib")
 
 
     #elif GetTarget() == 'windows':
     #elif GetTarget() == 'windows':
-    #    SDK["PYTHON"] = os.path.dirname(sysconfig.get_path("include"))
+    #    SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc())
     #    SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
     #    SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
     #    SDK["PYTHONEXEC"] = sys.executable
     #    SDK["PYTHONEXEC"] = sys.executable
 
 
     else:
     else:
-        SDK["PYTHON"] = sysconfig.get_path("include")
+        SDK["PYTHON"] = sysconfig.get_python_inc()
         SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags
         SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags
         SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
         SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
 
 
@@ -3439,8 +3442,8 @@ def GetCurrentPythonVersionInfo():
         "soabi": GetPythonABI(),
         "soabi": GetPythonABI(),
         "ext_suffix": GetExtensionSuffix(),
         "ext_suffix": GetExtensionSuffix(),
         "executable": sys.executable,
         "executable": sys.executable,
-        "purelib": sysconfig.get_path("purelib"),
-        "platlib": sysconfig.get_path("platlib"),
+        "purelib": sysconfig.get_python_lib(False),
+        "platlib": sysconfig.get_python_lib(True),
     }
     }
 
 
 
 

+ 6 - 1
makepanda/makewheel.py

@@ -562,6 +562,7 @@ class WheelFile(object):
             print("Adding {0} from {1}".format(target_path, orig_source_path))
             print("Adding {0} from {1}".format(target_path, orig_source_path))
 
 
         zinfo = zipfile.ZipInfo.from_file(source_path, target_path)
         zinfo = zipfile.ZipInfo.from_file(source_path, target_path)
+        zinfo.compress_type = self.zip_file.compression
         if zinfo.date_time > self.max_date_time:
         if zinfo.date_time > self.max_date_time:
             zinfo.date_time = self.max_date_time
             zinfo.date_time = self.max_date_time
 
 
@@ -653,7 +654,7 @@ def makewheel(version, output_dir, platform=None):
                     platform = platform.replace("linux", "manylinux2010")
                     platform = platform.replace("linux", "manylinux2010")
                 elif os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so"):
                 elif os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so"):
                     platform = platform.replace("linux", "manylinux2014")
                     platform = platform.replace("linux", "manylinux2014")
-                elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64/libc-2.24.so"):
+                elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so"):
                     platform = platform.replace("linux", "manylinux_2_24")
                     platform = platform.replace("linux", "manylinux_2_24")
 
 
     platform = platform.replace('-', '_').replace('.', '_')
     platform = platform.replace('-', '_').replace('.', '_')
@@ -785,6 +786,10 @@ if __debug__:
 
 
     for file in sorted(os.listdir(ext_mod_dir)):
     for file in sorted(os.listdir(ext_mod_dir)):
         if file.endswith(ext_suffix):
         if file.endswith(ext_suffix):
+            if file.startswith('_tkinter.'):
+                # Tkinter is supplied in a separate wheel.
+                continue
+
             source_path = os.path.join(ext_mod_dir, file)
             source_path = os.path.join(ext_mod_dir, file)
 
 
             if file.endswith('.pyd') and platform.startswith('cygwin'):
             if file.endswith('.pyd') and platform.startswith('cygwin'):

+ 6 - 1
makepanda/test_wheel.py

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

BIN
models/plugin_images/auth_click.png


BIN
models/plugin_images/auth_ready.png


BIN
models/plugin_images/auth_rollover.png


BIN
models/plugin_images/download.png


BIN
models/plugin_images/failed.png


BIN
models/plugin_images/installer.bmp


BIN
models/plugin_images/panda3d.icns


BIN
models/plugin_images/play_click.png


BIN
models/plugin_images/play_ready.png


BIN
models/plugin_images/play_rollover.png


+ 130 - 28
panda/src/androiddisplay/androidGraphicsWindow.cxx

@@ -45,6 +45,7 @@ AndroidGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
                       GraphicsStateGuardian *gsg,
                       GraphicsStateGuardian *gsg,
                       GraphicsOutput *host) :
                       GraphicsOutput *host) :
   GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
   GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
+  _primary_pointer_down(false),
   _mouse_button_state(0)
   _mouse_button_state(0)
 {
 {
   AndroidGraphicsPipe *android_pipe;
   AndroidGraphicsPipe *android_pipe;
@@ -516,12 +517,9 @@ handle_key_event(const AInputEvent *event) {
   // Is it an up or down event?
   // Is it an up or down event?
   int32_t action = AKeyEvent_getAction(event);
   int32_t action = AKeyEvent_getAction(event);
   if (action == AKEY_EVENT_ACTION_DOWN) {
   if (action == AKEY_EVENT_ACTION_DOWN) {
-    if (AKeyEvent_getRepeatCount(event) > 0) {
-      _input->button_resume_down(button);
-    } else {
-      _input->button_down(button);
-    }
-  } else if (action == AKEY_EVENT_ACTION_UP) {
+    _input->button_down(button);
+  }
+  else if (action == AKEY_EVENT_ACTION_UP) {
     _input->button_up(button);
     _input->button_up(button);
   }
   }
   // TODO AKEY_EVENT_ACTION_MULTIPLE
   // TODO AKEY_EVENT_ACTION_MULTIPLE
@@ -535,33 +533,101 @@ handle_key_event(const AInputEvent *event) {
 int32_t AndroidGraphicsWindow::
 int32_t AndroidGraphicsWindow::
 handle_motion_event(const AInputEvent *event) {
 handle_motion_event(const AInputEvent *event) {
   int32_t action = AMotionEvent_getAction(event);
   int32_t action = AMotionEvent_getAction(event);
+  int32_t pointer_index = (action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
   action &= AMOTION_EVENT_ACTION_MASK;
   action &= AMOTION_EVENT_ACTION_MASK;
 
 
-  if (action == AMOTION_EVENT_ACTION_DOWN ||
-      action == AMOTION_EVENT_ACTION_UP) {
-    // The up event doesn't let us know which button is up, so we need to
-    // keep track of the button state ourselves.
-    int32_t button_state = AMotionEvent_getButtonState(event);
-    if (button_state == 0 && action == AMOTION_EVENT_ACTION_DOWN) {
-      button_state = AMOTION_EVENT_BUTTON_PRIMARY;
+  if (action == AMOTION_EVENT_ACTION_DOWN) {
+    _primary_pointer_down = true;
+  }
+  else if (action == AMOTION_EVENT_ACTION_UP
+        || action == AMOTION_EVENT_ACTION_CANCEL) {
+    _primary_pointer_down = false;
+  }
+
+  int32_t button_state = AMotionEvent_getButtonState(event);
+
+  // Emulate mouse click; as long as the primary pointer is held down,
+  // and no other mouse button is causing it, the primary mouse button
+  // is considered depressed.
+  if (button_state == 0 && _primary_pointer_down &&
+      action != AMOTION_EVENT_ACTION_BUTTON_RELEASE) {
+    button_state |= AMOTION_EVENT_BUTTON_PRIMARY;
+  }
+
+  int32_t changed = _mouse_button_state ^ button_state;
+  if (changed != 0) {
+    if (changed & AMOTION_EVENT_BUTTON_PRIMARY) {
+      if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
+        _input->button_down(MouseButton::one());
+      } else {
+        _input->button_up(MouseButton::one());
+      }
     }
     }
-    int32_t changed = _mouse_button_state ^ button_state;
-    if (changed != 0) {
-      if (changed & AMOTION_EVENT_BUTTON_PRIMARY) {
-        if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
-          _input->button_down(MouseButton::one());
-        } else {
-          _input->button_up(MouseButton::one());
-        }
+    if (changed & AMOTION_EVENT_BUTTON_SECONDARY) {
+      if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
+        _input->button_down(MouseButton::three());
+      } else {
+        _input->button_up(MouseButton::three());
       }
       }
-      if (changed & AMOTION_EVENT_BUTTON_SECONDARY) {
-        if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
-          _input->button_down(MouseButton::three());
-        } else {
-          _input->button_up(MouseButton::three());
-        }
+    }
+    if (changed & AMOTION_EVENT_BUTTON_TERTIARY) {
+      if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) {
+        _input->button_down(MouseButton::two());
+      } else {
+        _input->button_up(MouseButton::two());
+      }
+    }
+    if (changed & AMOTION_EVENT_BUTTON_BACK) {
+      if (button_state & AMOTION_EVENT_BUTTON_BACK) {
+        _input->button_down(MouseButton::four());
+      } else {
+        _input->button_up(MouseButton::four());
+      }
+    }
+    if (changed & AMOTION_EVENT_BUTTON_FORWARD) {
+      if (button_state & AMOTION_EVENT_BUTTON_FORWARD) {
+        _input->button_down(MouseButton::five());
+      } else {
+        _input->button_up(MouseButton::five());
+      }
+    }
+    _mouse_button_state = button_state;
+  }
+
+  if (action == AMOTION_EVENT_ACTION_SCROLL) {
+    float v = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, pointer_index);
+    float h = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HSCROLL, pointer_index);
+
+    if (v > 0.5) {
+      while (v > 0) {
+        _input->button_down(MouseButton::wheel_up());
+        _input->button_up(MouseButton::wheel_up());
+        v -= 1;
+      }
+    }
+    else if (v < 0.5) {
+      while (v < 0) {
+        _input->button_down(MouseButton::wheel_down());
+        _input->button_up(MouseButton::wheel_down());
+        v += 1;
+      }
+    }
+
+    // In my tests the scroll direction is opposite of what the docs indicate,
+    // ie. a positive value is left, not right.
+    if (h > 0.5) {
+      while (h > 0) {
+        _input->button_down(MouseButton::wheel_left());
+        _input->button_up(MouseButton::wheel_left());
+        h -= 1;
+      }
+    }
+    else if (h < 0.5) {
+      while (h < 0) {
+        _input->button_down(MouseButton::wheel_right());
+        _input->button_up(MouseButton::wheel_right());
+        h += 1;
       }
       }
-      _mouse_button_state = button_state;
     }
     }
   }
   }
 
 
@@ -816,6 +882,42 @@ map_button(int32_t keycode) {
       return KeyboardButton::f12();
       return KeyboardButton::f12();
     case AKEYCODE_NUM_LOCK:
     case AKEYCODE_NUM_LOCK:
       return KeyboardButton::num_lock();
       return KeyboardButton::num_lock();
+    case AKEYCODE_NUMPAD_0:
+      return KeyboardButton::ascii_key('0');
+    case AKEYCODE_NUMPAD_1:
+      return KeyboardButton::ascii_key('1');
+    case AKEYCODE_NUMPAD_2:
+      return KeyboardButton::ascii_key('2');
+    case AKEYCODE_NUMPAD_3:
+      return KeyboardButton::ascii_key('3');
+    case AKEYCODE_NUMPAD_4:
+      return KeyboardButton::ascii_key('4');
+    case AKEYCODE_NUMPAD_5:
+      return KeyboardButton::ascii_key('5');
+    case AKEYCODE_NUMPAD_6:
+      return KeyboardButton::ascii_key('6');
+    case AKEYCODE_NUMPAD_7:
+      return KeyboardButton::ascii_key('7');
+    case AKEYCODE_NUMPAD_8:
+      return KeyboardButton::ascii_key('8');
+    case AKEYCODE_NUMPAD_9:
+      return KeyboardButton::ascii_key('9');
+    case AKEYCODE_NUMPAD_DIVIDE:
+      return KeyboardButton::ascii_key('/');
+    case AKEYCODE_NUMPAD_MULTIPLY:
+      return KeyboardButton::ascii_key('*');
+    case AKEYCODE_NUMPAD_SUBTRACT:
+      return KeyboardButton::ascii_key('-');
+    case AKEYCODE_NUMPAD_ADD:
+      return KeyboardButton::ascii_key('+');
+    case AKEYCODE_NUMPAD_DOT:
+      return KeyboardButton::ascii_key('.');
+    case AKEYCODE_NUMPAD_COMMA:
+      return KeyboardButton::ascii_key(',');
+    case AKEYCODE_NUMPAD_ENTER:
+      return KeyboardButton::enter();
+    case AKEYCODE_NUMPAD_EQUALS:
+      return KeyboardButton::ascii_key('=');
     default:
     default:
       break;
       break;
   }
   }

+ 1 - 0
panda/src/androiddisplay/androidGraphicsWindow.h

@@ -71,6 +71,7 @@ private:
   EGLDisplay _egl_display;
   EGLDisplay _egl_display;
   EGLSurface _egl_surface;
   EGLSurface _egl_surface;
 
 
+  bool _primary_pointer_down;
   int32_t _mouse_button_state;
   int32_t _mouse_button_state;
 
 
   GraphicsWindowInputDevice *_input;
   GraphicsWindowInputDevice *_input;

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

@@ -2158,6 +2158,7 @@ map_raw_key(unsigned short keycode) const {
   case 0x07: return KeyboardButton::ascii_key('x');
   case 0x07: return KeyboardButton::ascii_key('x');
   case 0x08: return KeyboardButton::ascii_key('c');
   case 0x08: return KeyboardButton::ascii_key('c');
   case 0x09: return KeyboardButton::ascii_key('v');
   case 0x09: return KeyboardButton::ascii_key('v');
+  case 0x0A: return KeyboardButton::ascii_key('<');
   case 0x0B: return KeyboardButton::ascii_key('b');
   case 0x0B: return KeyboardButton::ascii_key('b');
   case 0x0C: return KeyboardButton::ascii_key('q');
   case 0x0C: return KeyboardButton::ascii_key('q');
   case 0x0D: return KeyboardButton::ascii_key('w');
   case 0x0D: return KeyboardButton::ascii_key('w');

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

@@ -962,7 +962,7 @@ map_button(int code, DeviceClass device_class, int quirks) {
       KeyboardButton::ascii_key('.'),
       KeyboardButton::ascii_key('.'),
       ButtonHandle::none(),
       ButtonHandle::none(),
       ButtonHandle::none(),
       ButtonHandle::none(),
-      ButtonHandle::none(),
+      KeyboardButton::ascii_key('<'),
       KeyboardButton::f11(),
       KeyboardButton::f11(),
       KeyboardButton::f12(),
       KeyboardButton::f12(),
       ButtonHandle::none(),
       ButtonHandle::none(),

+ 11 - 12
panda/src/egg2pg/eggRenderState.cxx

@@ -162,18 +162,17 @@ fill_state(EggPrimitive *egg_prim) {
         // transform, never bake it in.  In fact, we don't even care about its
         // transform, never bake it in.  In fact, we don't even care about its
         // UV's in this case, since we won't be using them.
         // UV's in this case, since we won't be using them.
         tex_mat_attrib = apply_tex_mat(tex_mat_attrib, def._stage, egg_tex);
         tex_mat_attrib = apply_tex_mat(tex_mat_attrib, def._stage, egg_tex);
-
-      } else {
-/*
- * Otherwise, we need to record that there is at least one texture on this
- * particular UV name and with this particular texture matrix.  If there are
- * no other textures, or if all of the other textures use the same texture
- * matrix, then tex_mats[uv_name].size() will remain 1 (which tells us we can
- * bake in the texture matrix to the UV's).  On the other hand, if there is
- * another texture on the same uv name but with a different transform, it will
- * increase tex_mats[uv_name].size() to at least 2, indicating we can't bake
- * in the texture matrix.
- */
+      }
+      else {
+        // Otherwise, we need to record that there is at least one texture on
+        // this particular UV name and with this particular texture matrix.
+        // If there are no other textures, or if all of the other textures use
+        // the same texture matrix, then tex_mats[uv_name].size() will remain
+        // 1 (which tells us we can bake in the texture matrix to the UV's).
+        // On the other hand, if there is another texture on the same uv name
+        // but with a different transform, it will increase
+        // tex_mats[uv_name].size() to at least 2, indicating we can't bake in
+        // the texture matrix.
         tex_mats[uv_name][egg_tex->get_transform3d()].push_back(&def);
         tex_mats[uv_name][egg_tex->get_transform3d()].push_back(&def);
       }
       }
     }
     }

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

@@ -800,6 +800,9 @@ convert_primitive(const GeomVertexData *vertex_data,
   // Check for a texture.
   // Check for a texture.
   const TextureAttrib *ta;
   const TextureAttrib *ta;
   if (net_state->get_attrib(ta)) {
   if (net_state->get_attrib(ta)) {
+    const TexMatrixAttrib *tma = nullptr;
+    net_state->get_attrib(tma);
+
     for (size_t i = 0; i < ta->get_num_on_stages(); ++i) {
     for (size_t i = 0; i < ta->get_num_on_stages(); ++i) {
       TextureStage *tex_stage = ta->get_on_stage(i);
       TextureStage *tex_stage = ta->get_on_stage(i);
 
 
@@ -867,6 +870,45 @@ convert_primitive(const GeomVertexData *vertex_data,
           egg_tex->set_uv_name(name->get_basename());
           egg_tex->set_uv_name(name->get_basename());
         }
         }
 
 
+        if (tma != nullptr && tma->has_stage(tex_stage)) {
+          CPT(TransformState) transform = tma->get_transform(tex_stage);
+          if (!transform->is_identity()) {
+            if (transform->has_components()) {
+              // If the transform can be represented componentwise, we prefer storing
+              // it that way in the egg file.
+              const LVecBase3 &scale = transform->get_scale();
+              const LQuaternion &quat = transform->get_quat();
+              const LVecBase3 &pos = transform->get_pos();
+              if (!scale.almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) {
+                if (transform->is_2d()) {
+                  egg_tex->add_scale2d(LVecBase2d(scale[0], scale[1]));
+                } else {
+                  egg_tex->add_scale3d(LCAST(double, scale));
+                }
+              }
+              if (!quat.is_identity()) {
+                if (transform->is_2d()) {
+                  egg_tex->add_rotate2d(transform->get_rotate2d());
+                } else {
+                  egg_tex->add_rotate3d(LCAST(double, quat));
+                }
+              }
+              if (!pos.almost_equal(LVecBase3::zero())) {
+                if (transform->is_2d()) {
+                  egg_tex->add_translate2d(LVector2d(pos[0], pos[1]));
+                } else {
+                  egg_tex->add_translate3d(LCAST(double, pos));
+                }
+              }
+            }
+            else if (transform->has_mat()) {
+              // Otherwise, we store the raw matrix.
+              const LMatrix4 &mat = transform->get_mat();
+              egg_tex->set_transform3d(LCAST(double, mat));
+            }
+          }
+        }
+
         egg_prim->add_texture(egg_tex);
         egg_prim->add_texture(egg_tex);
       }
       }
     }
     }

+ 1 - 0
panda/src/event/asyncFuture.cxx

@@ -39,6 +39,7 @@ AsyncFuture::
   if (result_ref != nullptr) {
   if (result_ref != nullptr) {
     _result_ref.cheat() = nullptr;
     _result_ref.cheat() = nullptr;
     if (!result_ref->unref()) {
     if (!result_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete _result;
       delete _result;
     }
     }
     _result = nullptr;
     _result = nullptr;

+ 49 - 32
panda/src/express/referenceCount.I

@@ -27,9 +27,9 @@ TypeHandle RefCountObj<Base>::_type_handle;
  * inheritance.
  * inheritance.
  */
  */
 INLINE ReferenceCount::
 INLINE ReferenceCount::
-ReferenceCount() {
-  _weak_list = nullptr;
-  _ref_count = 0;
+ReferenceCount() :
+  _weak_list(nullptr),
+  _ref_count(0) {
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::record_pointer(this);
   MemoryUsage::record_pointer(this);
 #endif
 #endif
@@ -44,9 +44,9 @@ ReferenceCount() {
  * try.
  * try.
  */
  */
 INLINE ReferenceCount::
 INLINE ReferenceCount::
-ReferenceCount(const ReferenceCount &) {
-  _weak_list = nullptr;
-  _ref_count = 0;
+ReferenceCount(const ReferenceCount &) :
+  _weak_list(nullptr),
+  _ref_count(0) {
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::record_pointer(this);
   MemoryUsage::record_pointer(this);
 #endif
 #endif
@@ -69,7 +69,7 @@ operator = (const ReferenceCount &) {
   // to create an automatic (local variable) instance of a class that derives
   // to create an automatic (local variable) instance of a class that derives
   // from ReferenceCount.  Or maybe your headers are out of sync, and you need
   // from ReferenceCount.  Or maybe your headers are out of sync, and you need
   // to make clean in direct or some higher tree.
   // to make clean in direct or some higher tree.
-  nassertv(_ref_count != deleted_ref_count);
+  nassertv(_ref_count.load(std::memory_order_relaxed) != deleted_ref_count);
 }
 }
 
 
 /**
 /**
@@ -78,23 +78,29 @@ operator = (const ReferenceCount &) {
 ReferenceCount::
 ReferenceCount::
 ~ReferenceCount() {
 ~ReferenceCount() {
   TAU_PROFILE("ReferenceCount::~ReferenceCount()", " ", TAU_USER);
   TAU_PROFILE("ReferenceCount::~ReferenceCount()", " ", TAU_USER);
+
+  // We can safely use relaxed ordering for everything in this destructor,
+  // since (1) we already issued an acquire barrier before invoking delete,
+  // and (2) we are the only thread accessing this object at this point.
+  int ref_count = _ref_count.load(std::memory_order_relaxed);
+
   // If this assertion fails, we're trying to delete an object that was just
   // If this assertion fails, we're trying to delete an object that was just
   // deleted.  Possibly you used a real pointer instead of a PointerTo at some
   // deleted.  Possibly you used a real pointer instead of a PointerTo at some
   // point, and the object was deleted when the PointerTo went out of scope.
   // point, and the object was deleted when the PointerTo went out of scope.
   // Maybe you tried to create an automatic (local variable) instance of a
   // Maybe you tried to create an automatic (local variable) instance of a
   // class that derives from ReferenceCount.  Or maybe your headers are out of
   // class that derives from ReferenceCount.  Or maybe your headers are out of
   // sync, and you need to make clean in direct or some higher tree.
   // sync, and you need to make clean in direct or some higher tree.
-  nassertv(_ref_count != deleted_ref_count);
+  nassertv(ref_count != deleted_ref_count);
 
 
   // If this assertion fails, we're trying to delete a static object that
   // If this assertion fails, we're trying to delete a static object that
   // still has an outstanding reference count.  You should make sure that all
   // still has an outstanding reference count.  You should make sure that all
   // references to your static objects are gone by the time the object itself
   // references to your static objects are gone by the time the object itself
   // destructs.
   // destructs.
-  nassertv(_ref_count <= local_ref_count);
+  nassertv(ref_count <= local_ref_count);
 
 
   // If this assertion fails, the reference counts are all screwed up
   // If this assertion fails, the reference counts are all screwed up
   // altogether.  Maybe some errant code stomped all over memory somewhere.
   // altogether.  Maybe some errant code stomped all over memory somewhere.
-  nassertv(_ref_count >= 0);
+  nassertv(ref_count >= 0);
 
 
   // If this assertion fails, someone tried to delete this object while its
   // If this assertion fails, someone tried to delete this object while its
   // reference count was still positive.  Maybe you tried to point a PointerTo
   // reference count was still positive.  Maybe you tried to point a PointerTo
@@ -105,19 +111,20 @@ ReferenceCount::
   // Another possibility is you inadvertently omitted a copy constructor for a
   // Another possibility is you inadvertently omitted a copy constructor for a
   // ReferenceCount object, and then bitwise copied a dynamically allocated
   // ReferenceCount object, and then bitwise copied a dynamically allocated
   // value--reference count and all--onto a locally allocated one.
   // value--reference count and all--onto a locally allocated one.
-  nassertv(_ref_count == 0 || _ref_count == local_ref_count);
+  nassertv(ref_count == 0 || ref_count == local_ref_count);
 
 
   // Tell our weak reference holders that we're going away now.
   // Tell our weak reference holders that we're going away now.
-  if (_weak_list != nullptr) {
+  WeakReferenceList *weak_list = _weak_list.load(std::memory_order_relaxed);
+  if (weak_list != nullptr) {
     ((WeakReferenceList *)_weak_list)->mark_deleted();
     ((WeakReferenceList *)_weak_list)->mark_deleted();
-    _weak_list = nullptr;
+    _weak_list.store(nullptr, std::memory_order_release);
   }
   }
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
   // Ok, all clear to delete.  Now set the reference count to
   // Ok, all clear to delete.  Now set the reference count to
   // deleted_ref_count, so we'll have a better chance of noticing if we happen
   // deleted_ref_count, so we'll have a better chance of noticing if we happen
   // to have a stray pointer to it still out there.
   // to have a stray pointer to it still out there.
-  _ref_count = deleted_ref_count;
+  _ref_count.store(deleted_ref_count, std::memory_order_relaxed);
 #endif
 #endif
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
@@ -133,7 +140,7 @@ get_ref_count() const {
 #ifdef _DEBUG
 #ifdef _DEBUG
   test_ref_count_integrity();
   test_ref_count_integrity();
 #endif
 #endif
-  return (int)AtomicAdjust::get(_ref_count);
+  return _ref_count.load(std::memory_order_acquire);
 }
 }
 
 
 /**
 /**
@@ -154,7 +161,7 @@ ref() const {
   nassertv(test_ref_count_integrity());
   nassertv(test_ref_count_integrity());
 #endif
 #endif
 
 
-  AtomicAdjust::inc(_ref_count);
+  _ref_count.fetch_add(1, std::memory_order_relaxed);
 }
 }
 
 
 /**
 /**
@@ -184,9 +191,9 @@ unref() const {
   // If this assertion fails, you tried to unref an object with a zero
   // If this assertion fails, you tried to unref an object with a zero
   // reference count.  Are you using ref() and unref() directly?  Are you sure
   // reference count.  Are you using ref() and unref() directly?  Are you sure
   // you can't use PointerTo's?
   // you can't use PointerTo's?
-  nassertr(_ref_count > 0, 0);
+  nassertr(_ref_count.load(std::memory_order_relaxed) > 0, 0);
 #endif
 #endif
-  return AtomicAdjust::dec(_ref_count);
+  return _ref_count.fetch_sub(1, std::memory_order_release) != 1;
 }
 }
 
 
 /**
 /**
@@ -227,11 +234,11 @@ test_ref_count_nonzero() const {
  */
  */
 INLINE void ReferenceCount::
 INLINE void ReferenceCount::
 local_object() {
 local_object() {
+  int prev_count = _ref_count.exchange(local_ref_count, std::memory_order_relaxed);
+
   // If this assertion fails, you didn't call this immediately after creating
   // If this assertion fails, you didn't call this immediately after creating
   // a local object.
   // a local object.
-  nassertv(_ref_count == 0);
-
-  _ref_count = local_ref_count;
+  nassertv(prev_count == 0);
 }
 }
 
 
 /**
 /**
@@ -242,7 +249,7 @@ local_object() {
  */
  */
 INLINE bool ReferenceCount::
 INLINE bool ReferenceCount::
 has_weak_list() const {
 has_weak_list() const {
-  return _weak_list != nullptr;
+  return _weak_list.load(std::memory_order_relaxed) != nullptr;
 }
 }
 
 
 /**
 /**
@@ -255,10 +262,10 @@ has_weak_list() const {
  */
  */
 INLINE WeakReferenceList *ReferenceCount::
 INLINE WeakReferenceList *ReferenceCount::
 get_weak_list() const {
 get_weak_list() const {
-  if (AtomicAdjust::get_ptr(_weak_list) == nullptr) {
+  if (_weak_list.load(std::memory_order_relaxed) == nullptr) {
     ((ReferenceCount *)this)->create_weak_list();
     ((ReferenceCount *)this)->create_weak_list();
   }
   }
-  return (WeakReferenceList *)AtomicAdjust::get_ptr(_weak_list);
+  return _weak_list.load(std::memory_order_consume);
 }
 }
 
 
 /**
 /**
@@ -273,7 +280,7 @@ weak_ref() {
 #ifdef _DEBUG
 #ifdef _DEBUG
   nassertr(test_ref_count_integrity(), nullptr);
   nassertr(test_ref_count_integrity(), nullptr);
 #else
 #else
-  nassertr(_ref_count != deleted_ref_count, nullptr);
+  nassertr(_ref_count.load(std::memory_order_relaxed) != deleted_ref_count, nullptr);
 #endif
 #endif
   WeakReferenceList *weak_ref = get_weak_list();
   WeakReferenceList *weak_ref = get_weak_list();
   weak_ref->ref();
   weak_ref->ref();
@@ -290,7 +297,7 @@ weak_unref() {
 #ifdef _DEBUG
 #ifdef _DEBUG
   nassertv(test_ref_count_integrity());
   nassertv(test_ref_count_integrity());
 #endif
 #endif
-  WeakReferenceList *weak_list = (WeakReferenceList *)_weak_list;
+  WeakReferenceList *weak_list = _weak_list.load(std::memory_order_consume);
   nassertv(weak_list != nullptr);
   nassertv(weak_list != nullptr);
   bool nonzero = weak_list->unref();
   bool nonzero = weak_list->unref();
   nassertv(nonzero);
   nassertv(nonzero);
@@ -307,13 +314,16 @@ ref_if_nonzero() const {
 #ifdef _DEBUG
 #ifdef _DEBUG
   test_ref_count_integrity();
   test_ref_count_integrity();
 #endif
 #endif
-  AtomicAdjust::Integer ref_count;
+  int ref_count = _ref_count.load(std::memory_order_relaxed);
   do {
   do {
-    ref_count = AtomicAdjust::get(_ref_count);
     if (ref_count <= 0) {
     if (ref_count <= 0) {
       return false;
       return false;
     }
     }
-  } while (ref_count != AtomicAdjust::compare_and_exchange(_ref_count, ref_count, ref_count + 1));
+  }
+  while (!_ref_count.compare_exchange_weak(ref_count, ref_count + 1,
+                                           std::memory_order_seq_cst,
+                                           std::memory_order_relaxed));
+
   return true;
   return true;
 }
 }
 
 
@@ -321,15 +331,21 @@ ref_if_nonzero() const {
  * Atomically decreases the reference count of this object if it is one.
  * Atomically decreases the reference count of this object if it is one.
  * Do not use this.  This exists only to implement a special case with the
  * Do not use this.  This exists only to implement a special case with the
  * state cache.
  * state cache.
- * @return false if the reference count was decremented to zero.
+ * @return false on success, ie. if the reference count was decremented to 0.
  */
  */
 INLINE bool ReferenceCount::
 INLINE bool ReferenceCount::
 unref_if_one() const {
 unref_if_one() const {
 #ifdef _DEBUG
 #ifdef _DEBUG
   nassertr(test_ref_count_integrity(), 0);
   nassertr(test_ref_count_integrity(), 0);
-  nassertr(_ref_count > 0, 0);
+  nassertr(_ref_count.load(std::memory_order_relaxed) > 0, 0);
 #endif
 #endif
-  return (AtomicAdjust::compare_and_exchange(_ref_count, 1, 0) != 1);
+
+  // Presumably if the ref count becomes 0, someone is about to delete the
+  // object or something like that, hence the acquire order on success.
+  int expected = 1;
+  return !_ref_count.compare_exchange_strong(expected, 0,
+                                             std::memory_order_acquire,
+                                             std::memory_order_relaxed);
 }
 }
 
 
 /**
 /**
@@ -350,6 +366,7 @@ unref_delete(RefCountType *ptr) {
 
 
   if (!ptr->unref()) {
   if (!ptr->unref()) {
     // If the reference count has gone to zero, delete the object.
     // If the reference count has gone to zero, delete the object.
+    patomic_thread_fence(std::memory_order_acquire);
     delete ptr;
     delete ptr;
   }
   }
 }
 }

+ 11 - 8
panda/src/express/referenceCount.cxx

@@ -23,17 +23,19 @@ TypeHandle ReferenceCount::_type_handle;
  */
  */
 bool ReferenceCount::
 bool ReferenceCount::
 do_test_ref_count_integrity() const {
 do_test_ref_count_integrity() const {
+  int ref_count = _ref_count.load(std::memory_order_relaxed);
+
   // If this assertion fails, we're trying to delete an object that was just
   // If this assertion fails, we're trying to delete an object that was just
   // deleted.  Possibly you used a real pointer instead of a PointerTo at some
   // deleted.  Possibly you used a real pointer instead of a PointerTo at some
   // point, and the object was deleted when the PointerTo went out of scope.
   // point, and the object was deleted when the PointerTo went out of scope.
   // Maybe you tried to create an automatic (local variable) instance of a
   // Maybe you tried to create an automatic (local variable) instance of a
   // class that derives from ReferenceCount.  Or maybe your headers are out of
   // class that derives from ReferenceCount.  Or maybe your headers are out of
   // sync, and you need to make clean in direct or some higher tree.
   // sync, and you need to make clean in direct or some higher tree.
-  nassertr(_ref_count != deleted_ref_count, false);
+  nassertr(ref_count != deleted_ref_count, false);
 
 
   // If this assertion fails, the reference counts are all screwed up
   // If this assertion fails, the reference counts are all screwed up
   // altogether.  Maybe some errant code stomped all over memory somewhere.
   // altogether.  Maybe some errant code stomped all over memory somewhere.
-  nassertr(_ref_count >= 0, false);
+  nassertr(ref_count >= 0, false);
 
 
   return true;
   return true;
 }
 }
@@ -44,7 +46,7 @@ do_test_ref_count_integrity() const {
 bool ReferenceCount::
 bool ReferenceCount::
 do_test_ref_count_nonzero() const {
 do_test_ref_count_nonzero() const {
   nassertr(do_test_ref_count_integrity(), false);
   nassertr(do_test_ref_count_integrity(), false);
-  nassertr(_ref_count > 0, false);
+  nassertr(_ref_count.load(std::memory_order_relaxed) > 0, false);
 
 
   return true;
   return true;
 }
 }
@@ -54,11 +56,12 @@ do_test_ref_count_nonzero() const {
  */
  */
 void ReferenceCount::
 void ReferenceCount::
 create_weak_list() {
 create_weak_list() {
-  WeakReferenceList *weak_list = new WeakReferenceList;
-  void *orig =
-    AtomicAdjust::compare_and_exchange_ptr(_weak_list, nullptr, weak_list);
-  if (orig != nullptr) {
+  WeakReferenceList *new_list = new WeakReferenceList;
+  WeakReferenceList *old_list = nullptr;
+  if (!_weak_list.compare_exchange_strong(old_list, new_list,
+                                          std::memory_order_release,
+                                          std::memory_order_relaxed)) {
     // Someone else created it first.
     // Someone else created it first.
-    delete weak_list;
+    delete new_list;
   }
   }
 }
 }

+ 3 - 2
panda/src/express/referenceCount.h

@@ -23,6 +23,7 @@
 #include "atomicAdjust.h"
 #include "atomicAdjust.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
 #include "deletedChain.h"
 #include "deletedChain.h"
+#include "patomic.h"
 
 
 #include <stdlib.h>
 #include <stdlib.h>
 
 
@@ -89,8 +90,8 @@ private:
     deleted_ref_count = -100,
     deleted_ref_count = -100,
   };
   };
 
 
-  mutable AtomicAdjust::Integer _ref_count;
-  AtomicAdjust::Pointer _weak_list;  // WeakReferenceList *
+  mutable patomic<int> _ref_count;
+  patomic<WeakReferenceList *> _weak_list;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 7 - 0
panda/src/express/weakPointerToBase.I

@@ -117,6 +117,7 @@ INLINE WeakPointerToBase<T>::
 ~WeakPointerToBase() {
 ~WeakPointerToBase() {
   WeakReferenceList *old_ref = (WeakReferenceList *)_weak_ref;
   WeakReferenceList *old_ref = (WeakReferenceList *)_weak_ref;
   if (old_ref != nullptr && !old_ref->unref()) {
   if (old_ref != nullptr && !old_ref->unref()) {
+    patomic_thread_fence(std::memory_order_acquire);
     delete old_ref;
     delete old_ref;
   }
   }
 }
 }
@@ -143,6 +144,7 @@ reassign(To *ptr) {
 
 
     // Now remove the old reference.
     // Now remove the old reference.
     if (old_ref != nullptr && !old_ref->unref()) {
     if (old_ref != nullptr && !old_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete old_ref;
       delete old_ref;
     }
     }
   }
   }
@@ -183,6 +185,7 @@ reassign(const WeakPointerToBase<To> &copy) {
 
 
     // Now remove the old reference.
     // Now remove the old reference.
     if (old_ref != nullptr && !old_ref->unref()) {
     if (old_ref != nullptr && !old_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete old_ref;
       delete old_ref;
     }
     }
   }
   }
@@ -205,6 +208,7 @@ reassign(WeakPointerToBase<To> &&from) noexcept {
 
 
     // Now delete the old pointer.
     // Now delete the old pointer.
     if (old_ref != nullptr && !old_ref->unref()) {
     if (old_ref != nullptr && !old_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete old_ref;
       delete old_ref;
     }
     }
   }
   }
@@ -233,6 +237,7 @@ reassign(const WeakPointerToBase<Y> &copy) {
 
 
     // Now remove the old reference.
     // Now remove the old reference.
     if (old_ref != nullptr && !old_ref->unref()) {
     if (old_ref != nullptr && !old_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete old_ref;
       delete old_ref;
     }
     }
   }
   }
@@ -260,6 +265,7 @@ reassign(WeakPointerToBase<Y> &&from) noexcept {
 
 
     // Now delete the old pointer.
     // Now delete the old pointer.
     if (old_ref != nullptr && !old_ref->unref()) {
     if (old_ref != nullptr && !old_ref->unref()) {
+      patomic_thread_fence(std::memory_order_acquire);
       delete old_ref;
       delete old_ref;
     }
     }
   }
   }
@@ -627,6 +633,7 @@ clear() {
 
 
   // Now remove the old reference.
   // Now remove the old reference.
   if (old_ref != nullptr && !old_ref->unref()) {
   if (old_ref != nullptr && !old_ref->unref()) {
+    patomic_thread_fence(std::memory_order_acquire);
     delete old_ref;
     delete old_ref;
   }
   }
 }
 }

+ 3 - 3
panda/src/express/weakReferenceList.I

@@ -18,7 +18,7 @@
  */
  */
 INLINE void WeakReferenceList::
 INLINE void WeakReferenceList::
 ref() const {
 ref() const {
-  AtomicAdjust::inc(_count);
+  _count.fetch_add(1, std::memory_order_relaxed);
 }
 }
 
 
 /**
 /**
@@ -30,7 +30,7 @@ ref() const {
  */
  */
 INLINE bool WeakReferenceList::
 INLINE bool WeakReferenceList::
 unref() const {
 unref() const {
-  return AtomicAdjust::dec(_count);
+  return _count.fetch_sub(1, std::memory_order_release) != 1;
 }
 }
 
 
 /**
 /**
@@ -41,5 +41,5 @@ unref() const {
  */
  */
 INLINE bool WeakReferenceList::
 INLINE bool WeakReferenceList::
 was_deleted() const {
 was_deleted() const {
-  return AtomicAdjust::get(_count) < _alive_offset;
+  return _count.load(std::memory_order_relaxed) < _alive_offset;
 }
 }

+ 2 - 2
panda/src/express/weakReferenceList.cxx

@@ -27,7 +27,7 @@ WeakReferenceList() : _count(_alive_offset) {
  */
  */
 WeakReferenceList::
 WeakReferenceList::
 ~WeakReferenceList() {
 ~WeakReferenceList() {
-  nassertv(_count == 0);
+  nassertv(_count.load(std::memory_order_relaxed) == 0);
 }
 }
 
 
 /**
 /**
@@ -91,7 +91,7 @@ mark_deleted() {
 
 
   // Decrement the special offset added to the weak pointer count to indicate
   // Decrement the special offset added to the weak pointer count to indicate
   // that it can be deleted when all the weak references have gone.
   // that it can be deleted when all the weak references have gone.
-  AtomicAdjust::Integer result = AtomicAdjust::add(_count, -_alive_offset);
+  int result = _count.fetch_sub(_alive_offset, std::memory_order_relaxed) - _alive_offset;
   _lock.unlock();
   _lock.unlock();
   if (result == 0) {
   if (result == 0) {
     // There are no weak references remaining either, so delete this.
     // There are no weak references remaining either, so delete this.

+ 3 - 2
panda/src/express/weakReferenceList.h

@@ -17,6 +17,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 #include "pmap.h"
 #include "pmap.h"
 #include "mutexImpl.h"
 #include "mutexImpl.h"
+#include "patomic.h"
 
 
 class WeakPointerCallback;
 class WeakPointerCallback;
 
 
@@ -53,8 +54,8 @@ private:
   // This has a very large number added to it if the object is still alive.
   // This has a very large number added to it if the object is still alive.
   // It could be 1, but having it be a large number makes it easy to check
   // It could be 1, but having it be a large number makes it easy to check
   // whether the object has been deleted or not.
   // whether the object has been deleted or not.
-  static const AtomicAdjust::Integer _alive_offset = (1 << 30);
-  mutable AtomicAdjust::Integer _count;
+  static const int _alive_offset = (1 << 30);
+  mutable patomic<int> _count;
 
 
   friend class ReferenceCount;
   friend class ReferenceCount;
 };
 };

+ 4 - 0
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1896,6 +1896,10 @@ reset() {
   else {
   else {
     _glFramebufferTexture = nullptr;
     _glFramebufferTexture = nullptr;
   }
   }
+
+  if (_supports_geometry_shaders) {
+    _supported_geom_rendering |= Geom::GR_adjacency;
+  }
 #endif
 #endif
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1

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

@@ -604,6 +604,14 @@ get_modified() const {
   return _cdata->_modified;
   return _cdata->_modified;
 }
 }
 
 
+/**
+ *
+ */
+INLINE const GeomVertexArrayData *GeomPrimitivePipelineReader::
+get_vertices() const {
+  return _vertices.p();
+}
+
 /**
 /**
  *
  *
  */
  */

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

@@ -2362,6 +2362,25 @@ get_num_primitives() const {
   }
   }
 }
 }
 
 
+/**
+ *
+ */
+int GeomPrimitivePipelineReader::
+get_num_faces() const {
+  int num_vertices_per_primitive = _object->get_num_vertices_per_primitive();
+
+  if (num_vertices_per_primitive == 0) {
+    int num_primitives = _cdata->_ends.size();
+    int num_vertices = get_num_vertices();
+    int min_num_vertices_per_primitive = _object->get_min_num_vertices_per_primitive();
+    int num_unused_vertices_per_primitive = _object->get_num_unused_vertices_per_primitive();
+    return num_vertices - (num_primitives * (min_num_vertices_per_primitive - 1)) - ((num_primitives - 1) * num_unused_vertices_per_primitive);
+  } else {
+    // Same as the number of primitives.
+    return (get_num_vertices() / num_vertices_per_primitive);
+  }
+}
+
 /**
 /**
  * Turns on all the bits corresponding to the vertices that are referenced
  * Turns on all the bits corresponding to the vertices that are referenced
  * by this GeomPrimitive.
  * by this GeomPrimitive.

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

@@ -372,12 +372,14 @@ public:
   INLINE int get_num_vertices() const;
   INLINE int get_num_vertices() const;
   int get_vertex(int i) const;
   int get_vertex(int i) const;
   int get_num_primitives() const;
   int get_num_primitives() const;
+  int get_num_faces() const;
   void get_referenced_vertices(BitArray &bits) const;
   void get_referenced_vertices(BitArray &bits) const;
   INLINE int get_min_vertex() const;
   INLINE int get_min_vertex() const;
   INLINE int get_max_vertex() const;
   INLINE int get_max_vertex() const;
   INLINE int get_data_size_bytes() const;
   INLINE int get_data_size_bytes() const;
   INLINE UpdateSeq get_modified() const;
   INLINE UpdateSeq get_modified() const;
   bool check_valid(const GeomVertexDataPipelineReader *data_reader) const;
   bool check_valid(const GeomVertexDataPipelineReader *data_reader) const;
+  INLINE const GeomVertexArrayData *get_vertices() const;
   INLINE int get_index_stride() const;
   INLINE int get_index_stride() const;
   INLINE const unsigned char *get_read_pointer(bool force) const;
   INLINE const unsigned char *get_read_pointer(bool force) const;
   INLINE int get_strip_cut_index() const;
   INLINE int get_strip_cut_index() const;

+ 18 - 0
panda/src/gobj/texture.cxx

@@ -3680,6 +3680,12 @@ do_read_txo_file(CData *cdata, const Filename &fullpath) {
   }
   }
 
 
   istream *in = file->open_read_file(true);
   istream *in = file->open_read_file(true);
+  if (in == nullptr) {
+    gobj_cat.error()
+      << "Failed to open " << filename << " for reading.\n";
+    return false;
+  }
+
   bool success = do_read_txo(cdata, *in, fullpath);
   bool success = do_read_txo(cdata, *in, fullpath);
   vfs->close_read_file(in);
   vfs->close_read_file(in);
 
 
@@ -3735,6 +3741,12 @@ do_read_dds_file(CData *cdata, const Filename &fullpath, bool header_only) {
   }
   }
 
 
   istream *in = file->open_read_file(true);
   istream *in = file->open_read_file(true);
+  if (in == nullptr) {
+    gobj_cat.error()
+      << "Failed to open " << filename << " for reading.\n";
+    return false;
+  }
+
   bool success = do_read_dds(cdata, *in, fullpath, header_only);
   bool success = do_read_dds(cdata, *in, fullpath, header_only);
   vfs->close_read_file(in);
   vfs->close_read_file(in);
 
 
@@ -4415,6 +4427,12 @@ do_read_ktx_file(CData *cdata, const Filename &fullpath, bool header_only) {
   }
   }
 
 
   istream *in = file->open_read_file(true);
   istream *in = file->open_read_file(true);
+  if (in == nullptr) {
+    gobj_cat.error()
+      << "Failed to open " << filename << " for reading.\n";
+    return false;
+  }
+
   bool success = do_read_ktx(cdata, *in, fullpath, header_only);
   bool success = do_read_ktx(cdata, *in, fullpath, header_only);
   vfs->close_read_file(in);
   vfs->close_read_file(in);
 
 

+ 2 - 2
panda/src/gobj/texturePeeker.cxx

@@ -436,7 +436,7 @@ lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
  * rectangle defined by the specified coordinate range.
  * rectangle defined by the specified coordinate range.
  *
  *
  * The texel color is linearly filtered over the entire region.  u, v, and w
  * The texel color is linearly filtered over the entire region.  u, v, and w
- * will wrap around regardless of the texture's wrap mode.
+ * must be in the range [0, 1].
  */
  */
 void TexturePeeker::
 void TexturePeeker::
 filter_rect(LColor &color,
 filter_rect(LColor &color,
@@ -464,7 +464,7 @@ filter_rect(LColor &color,
  * rectangle defined by the specified coordinate range.
  * rectangle defined by the specified coordinate range.
  *
  *
  * The texel color is linearly filtered over the entire region.  u, v, and w
  * The texel color is linearly filtered over the entire region.  u, v, and w
- * will wrap around regardless of the texture's wrap mode.
+ * must be in the range [0, 1].
  */
  */
 void TexturePeeker::
 void TexturePeeker::
 filter_rect(LColor &color,
 filter_rect(LColor &color,

+ 1 - 1
panda/src/pgraph/cacheStats.I

@@ -90,6 +90,6 @@ add_total_size(int count) {
 INLINE void CacheStats::
 INLINE void CacheStats::
 add_num_states(int count) {
 add_num_states(int count) {
 #ifndef NDEBUG
 #ifndef NDEBUG
-  _num_states += count;
+  _num_states.fetch_add(count, std::memory_order_relaxed);
 #endif  // NDEBUG
 #endif  // NDEBUG
 }
 }

+ 3 - 2
panda/src/pgraph/cacheStats.cxx

@@ -51,12 +51,13 @@ reset(double now) {
 void CacheStats::
 void CacheStats::
 write(std::ostream &out, const char *name) const {
 write(std::ostream &out, const char *name) const {
 #ifndef NDEBUG
 #ifndef NDEBUG
+  int num_states = _num_states.load(std::memory_order_relaxed);
   out << name << " cache: " << _cache_hits << " hits, "
   out << name << " cache: " << _cache_hits << " hits, "
       << _cache_misses << " misses\n"
       << _cache_misses << " misses\n"
       << _cache_adds + _cache_new_adds << "(" << _cache_new_adds << ") adds(new), "
       << _cache_adds + _cache_new_adds << "(" << _cache_new_adds << ") adds(new), "
       << _cache_dels << " dels, "
       << _cache_dels << " dels, "
-      << _total_cache_size << " / " << _num_states << " = "
-      << (double)_total_cache_size / (double)_num_states
+      << _total_cache_size << " / " << num_states << " = "
+      << (double)_total_cache_size / (double)num_states
       << " average cache size\n";
       << " average cache size\n";
 #endif  // NDEBUG
 #endif  // NDEBUG
 }
 }

+ 2 - 1
panda/src/pgraph/cacheStats.h

@@ -16,6 +16,7 @@
 
 
 #include "pandabase.h"
 #include "pandabase.h"
 #include "clockObject.h"
 #include "clockObject.h"
+#include "patomic.h"
 #include "pnotify.h"
 #include "pnotify.h"
 
 
 /**
 /**
@@ -45,7 +46,7 @@ private:
   int _cache_new_adds = 0;
   int _cache_new_adds = 0;
   int _cache_dels = 0;
   int _cache_dels = 0;
   int _total_cache_size = 0;
   int _total_cache_size = 0;
-  int _num_states = 0;
+  patomic<int> _num_states {0};
   double _last_reset = 0.0;
   double _last_reset = 0.0;
 
 
   bool _cache_report = false;
   bool _cache_report = false;

+ 2 - 0
panda/src/pgraph/lightAttrib.cxx

@@ -469,6 +469,7 @@ replace_on_light(const NodePath &source, const NodePath &dest) const {
     slobj->attrib_unref();
     slobj->attrib_unref();
 
 
     *it = dest;
     *it = dest;
+    attrib->_on_lights.sort();
   }
   }
   return return_new(attrib);
   return return_new(attrib);
 }
 }
@@ -531,6 +532,7 @@ replace_off_light(const NodePath &source, const NodePath &dest) const {
     slobj->attrib_unref();
     slobj->attrib_unref();
 
 
     *it = dest;
     *it = dest;
+    attrib->_off_lights.sort();
   }
   }
   return return_new(attrib);
   return return_new(attrib);
 }
 }

+ 1 - 1
panda/src/pgraph/pandaNode.cxx

@@ -1306,7 +1306,7 @@ compare_tags(const PandaNode *other) const {
       return cmp;
       return cmp;
     }
     }
 
 
-    cmp = strcmp(a_data.get_key(ai).c_str(), b_data.get_key(bi).c_str());
+    cmp = strcmp(a_data.get_data(ai).c_str(), b_data.get_data(bi).c_str());
     if (cmp != 0) {
     if (cmp != 0) {
       return cmp;
       return cmp;
     }
     }

+ 4 - 0
panda/src/pgraph/pythonLoaderFileType.cxx

@@ -158,8 +158,12 @@ init(PyObject *loader) {
     }
     }
     Py_DECREF(supports_compressed);
     Py_DECREF(supports_compressed);
   }
   }
+  else {
+    PyErr_Clear();
+  }
 
 
   _load_func = PyObject_GetAttrString(loader, "load_file");
   _load_func = PyObject_GetAttrString(loader, "load_file");
+  PyErr_Clear();
   _save_func = PyObject_GetAttrString(loader, "save_file");
   _save_func = PyObject_GetAttrString(loader, "save_file");
   PyErr_Clear();
   PyErr_Clear();
 
 

+ 0 - 2
panda/src/pgraph/renderState.cxx

@@ -117,8 +117,6 @@ RenderState::
   nassertv(!is_destructing());
   nassertv(!is_destructing());
   set_destructing();
   set_destructing();
 
 
-  LightReMutexHolder holder(*_states_lock);
-
   // unref() should have cleared these.
   // unref() should have cleared these.
   nassertv(_saved_entry == -1);
   nassertv(_saved_entry == -1);
   nassertv(_composition_cache.is_empty() && _invert_composition_cache.is_empty());
   nassertv(_composition_cache.is_empty() && _invert_composition_cache.is_empty());

+ 36 - 41
panda/src/pgraphnodes/sceneGraphAnalyzer.cxx

@@ -381,6 +381,7 @@ collect_statistics(GeomNode *geom_node) {
  */
  */
 void SceneGraphAnalyzer::
 void SceneGraphAnalyzer::
 collect_statistics(const Geom *geom) {
 collect_statistics(const Geom *geom) {
+  Thread *current_thread = Thread::get_current_thread();
   CPT(GeomVertexData) vdata = geom->get_vertex_data();
   CPT(GeomVertexData) vdata = geom->get_vertex_data();
   std::pair<VDatas::iterator, bool> result = _vdatas.insert(VDatas::value_type(vdata, VDataTracker()));
   std::pair<VDatas::iterator, bool> result = _vdatas.insert(VDatas::value_type(vdata, VDataTracker()));
   if (result.second) {
   if (result.second) {
@@ -408,7 +409,7 @@ collect_statistics(const Geom *geom) {
     }
     }
     if (format->has_column(InternalName::get_normal())) {
     if (format->has_column(InternalName::get_normal())) {
       _num_normals += num_rows;
       _num_normals += num_rows;
-      GeomVertexReader rnormal(vdata, InternalName::get_normal());
+      GeomVertexReader rnormal(vdata, InternalName::get_normal(), current_thread);
       while (!rnormal.is_at_end()) {
       while (!rnormal.is_at_end()) {
         LVector3f normal = rnormal.get_data3f();
         LVector3f normal = rnormal.get_data3f();
         float length = normal.length();
         float length = normal.length();
@@ -439,52 +440,46 @@ collect_statistics(const Geom *geom) {
   int num_primitives = geom->get_num_primitives();
   int num_primitives = geom->get_num_primitives();
   for (int i = 0; i < num_primitives; ++i) {
   for (int i = 0; i < num_primitives; ++i) {
     CPT(GeomPrimitive) prim = geom->get_primitive(i);
     CPT(GeomPrimitive) prim = geom->get_primitive(i);
+    GeomPrimitivePipelineReader reader(prim, current_thread);
+    reader.get_referenced_vertices(tracker._referenced_vertices);
 
 
-    int num_vertices = prim->get_num_vertices();
-    int strip_cut_index = prim->get_strip_cut_index();
-    for (int vi = 0; vi < num_vertices; ++vi) {
-      int index = prim->get_vertex(vi);
-      if (index != strip_cut_index) {
-        tracker._referenced_vertices.set_bit(index);
-      }
-    }
-
-    if (prim->is_indexed()) {
-      collect_prim_statistics(prim->get_vertices());
+    if (reader.is_indexed()) {
+      collect_prim_statistics(reader.get_vertices());
       if (prim->is_composite()) {
       if (prim->is_composite()) {
-        collect_statistics(prim->get_mins());
-        collect_statistics(prim->get_maxs());
+        reader.check_minmax();
+        collect_statistics(reader.get_mins());
+        collect_statistics(reader.get_maxs());
       }
       }
     }
     }
 
 
     if (prim->is_of_type(GeomPoints::get_class_type())) {
     if (prim->is_of_type(GeomPoints::get_class_type())) {
-      _num_points += prim->get_num_primitives();
-
-    } else if (prim->is_of_type(GeomLines::get_class_type())) {
-      _num_lines += prim->get_num_primitives();
-
-    } else if (prim->is_of_type(GeomLinestrips::get_class_type())) {
-      _num_lines += prim->get_num_faces();
-
-    } else if (prim->is_of_type(GeomTriangles::get_class_type())) {
-      _num_tris += prim->get_num_primitives();
-      _num_individual_tris += prim->get_num_primitives();
-
-    } else if (prim->is_of_type(GeomTristrips::get_class_type())) {
-      _num_tris += prim->get_num_faces();
-      _num_tristrips += prim->get_num_primitives();
-      _num_triangles_in_strips += prim->get_num_faces();
-
-    } else if (prim->is_of_type(GeomTrifans::get_class_type())) {
-      _num_tris += prim->get_num_faces();
-      _num_trifans += prim->get_num_primitives();
-      _num_triangles_in_fans += prim->get_num_faces();
-
-    } else if (prim->is_of_type(GeomPatches::get_class_type())) {
-      _num_patches += prim->get_num_primitives();
-      _num_vertices_in_patches += prim->get_num_vertices();
-
-    } else {
+      _num_points += reader.get_num_primitives();
+    }
+    else if (prim->is_of_type(GeomLines::get_class_type())) {
+      _num_lines += reader.get_num_primitives();
+    }
+    else if (prim->is_of_type(GeomLinestrips::get_class_type())) {
+      _num_lines += reader.get_num_faces();
+    }
+    else if (prim->is_of_type(GeomTriangles::get_class_type())) {
+      _num_tris += reader.get_num_primitives();
+      _num_individual_tris += reader.get_num_primitives();
+    }
+    else if (prim->is_of_type(GeomTristrips::get_class_type())) {
+      _num_tris += reader.get_num_faces();
+      _num_tristrips += reader.get_num_primitives();
+      _num_triangles_in_strips += reader.get_num_faces();
+    }
+    else if (prim->is_of_type(GeomTrifans::get_class_type())) {
+      _num_tris += reader.get_num_faces();
+      _num_trifans += reader.get_num_primitives();
+      _num_triangles_in_fans += reader.get_num_faces();
+    }
+    else if (prim->is_of_type(GeomPatches::get_class_type())) {
+      _num_patches += reader.get_num_primitives();
+      _num_vertices_in_patches += reader.get_num_vertices();
+    }
+    else {
       pgraph_cat.warning()
       pgraph_cat.warning()
         << "Unknown GeomPrimitive type in SceneGraphAnalyzer: "
         << "Unknown GeomPrimitive type in SceneGraphAnalyzer: "
         << prim->get_type() << "\n";
         << prim->get_type() << "\n";

+ 2 - 5
panda/src/pgui/pgScrollFrame.h

@@ -19,10 +19,7 @@
 #include "pgVirtualFrame.h"
 #include "pgVirtualFrame.h"
 #include "pgSliderBarNotify.h"
 #include "pgSliderBarNotify.h"
 #include "pgSliderBar.h"
 #include "pgSliderBar.h"
-
-#ifdef PHAVE_ATOMIC
-#include <atomic>
-#endif
+#include "patomic.h"
 
 
 /**
 /**
  * This is a special kind of frame that pretends to be much larger than it
  * This is a special kind of frame that pretends to be much larger than it
@@ -96,7 +93,7 @@ private:
 private:
 private:
   bool _needs_remanage;
   bool _needs_remanage;
   bool _needs_recompute_clip;
   bool _needs_recompute_clip;
-  std::atomic_flag _canvas_computed;
+  patomic_flag _canvas_computed;
 
 
   bool _has_virtual_frame;
   bool _has_virtual_frame;
   LVecBase4 _virtual_frame;
   LVecBase4 _virtual_frame;

+ 2 - 5
panda/src/physics/physicalNode.cxx

@@ -13,13 +13,10 @@
 
 
 #include "physicalNode.h"
 #include "physicalNode.h"
 #include "physicsManager.h"
 #include "physicsManager.h"
-
-#ifdef PHAVE_ATOMIC
-#include <atomic>
-#endif
+#include "patomic.h"
 
 
 // static stuff.
 // static stuff.
-static std::atomic_flag warned_copy_physical_node = ATOMIC_FLAG_INIT;
+static patomic_flag warned_copy_physical_node = ATOMIC_FLAG_INIT;
 
 
 TypeHandle PhysicalNode::_type_handle;
 TypeHandle PhysicalNode::_type_handle;
 
 

+ 0 - 16
panda/src/pipeline/conditionVarDummyImpl.I

@@ -25,22 +25,6 @@ INLINE ConditionVarDummyImpl::
 ~ConditionVarDummyImpl() {
 ~ConditionVarDummyImpl() {
 }
 }
 
 
-/**
- *
- */
-INLINE void ConditionVarDummyImpl::
-wait() {
-  Thread::force_yield();
-}
-
-/**
- *
- */
-INLINE void ConditionVarDummyImpl::
-wait(double) {
-  Thread::force_yield();
-}
-
 /**
 /**
  *
  *
  */
  */

+ 17 - 0
panda/src/pipeline/conditionVarDummyImpl.cxx

@@ -13,3 +13,20 @@
 
 
 #include "selectThreadImpl.h"
 #include "selectThreadImpl.h"
 #include "conditionVarDummyImpl.h"
 #include "conditionVarDummyImpl.h"
+#include "thread.h"
+
+/**
+ *
+ */
+void ConditionVarDummyImpl::
+wait() {
+  Thread::force_yield();
+}
+
+/**
+ *
+ */
+void ConditionVarDummyImpl::
+wait(double) {
+  Thread::force_yield();
+}

+ 2 - 3
panda/src/pipeline/conditionVarDummyImpl.h

@@ -16,7 +16,6 @@
 
 
 #include "pandabase.h"
 #include "pandabase.h"
 #include "selectThreadImpl.h"
 #include "selectThreadImpl.h"
-#include "thread.h"
 
 
 #include "pnotify.h"
 #include "pnotify.h"
 
 
@@ -31,8 +30,8 @@ public:
   INLINE ConditionVarDummyImpl(MutexDummyImpl &mutex);
   INLINE ConditionVarDummyImpl(MutexDummyImpl &mutex);
   INLINE ~ConditionVarDummyImpl();
   INLINE ~ConditionVarDummyImpl();
 
 
-  INLINE void wait();
-  INLINE void wait(double timeout);
+  void wait();
+  void wait(double timeout);
   INLINE void notify();
   INLINE void notify();
   INLINE void notify_all();
   INLINE void notify_all();
 };
 };

+ 2 - 1
panda/src/pipeline/thread.h

@@ -42,7 +42,8 @@ class AsyncTask;
  * object will automatically be destructed if no other pointers are
  * object will automatically be destructed if no other pointers are
  * referencing it.
  * referencing it.
  */
  */
-class EXPCL_PANDA_PIPELINE Thread : public TypedReferenceCount, public Namable {
+// Due to a GCC bug, we can't use alignas() together with an attribute.
+class ALIGN_64BYTE EXPCL_PANDA_PIPELINE Thread : public TypedReferenceCount, public Namable {
 protected:
 protected:
   Thread(const std::string &name, const std::string &sync_name);
   Thread(const std::string &name, const std::string &sync_name);
   Thread(const Thread &copy) = delete;
   Thread(const Thread &copy) = delete;

+ 2 - 20
panda/src/pipeline/threadPosixImpl.I

@@ -46,26 +46,8 @@ prepare_for_exit() {
 INLINE Thread *ThreadPosixImpl::
 INLINE Thread *ThreadPosixImpl::
 get_current_thread() {
 get_current_thread() {
   TAU_PROFILE("Thread *ThreadPosixImpl::get_current_thread()", " ", TAU_USER);
   TAU_PROFILE("Thread *ThreadPosixImpl::get_current_thread()", " ", TAU_USER);
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-  return (Thread *)pthread_getspecific(_pt_ptr_index);
-}
-
-/**
- * Associates the indicated Thread object with the currently-executing thread.
- * You should not call this directly; use Thread::bind_thread() instead.
- */
-INLINE void ThreadPosixImpl::
-bind_thread(Thread *thread) {
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-  int result = pthread_setspecific(_pt_ptr_index, thread);
-  nassertv(result == 0);
-#ifdef ANDROID
-  bind_java_thread();
-#endif
+  Thread *thread = _current_thread;
+  return (thread != nullptr) ? thread : init_current_thread();
 }
 }
 
 
 /**
 /**

+ 28 - 27
panda/src/pipeline/threadPosixImpl.cxx

@@ -28,8 +28,8 @@
 static JavaVM *java_vm = nullptr;
 static JavaVM *java_vm = nullptr;
 #endif
 #endif
 
 
-pthread_key_t ThreadPosixImpl::_pt_ptr_index = 0;
-bool ThreadPosixImpl::_got_pt_ptr_index = false;
+__thread Thread *ThreadPosixImpl::_current_thread = nullptr;
+static patomic_flag _main_thread_known = ATOMIC_FLAG_INIT;
 
 
 /**
 /**
  *
  *
@@ -80,10 +80,6 @@ start(ThreadPriority priority, bool joinable) {
   _status = S_start_called;
   _status = S_start_called;
   _detached = false;
   _detached = false;
 
 
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-
   pthread_attr_t attr;
   pthread_attr_t attr;
   pthread_attr_init(&attr);
   pthread_attr_init(&attr);
 
 
@@ -186,6 +182,21 @@ get_unique_id() const {
   return strm.str();
   return strm.str();
 }
 }
 
 
+/**
+ * Associates the indicated Thread object with the currently-executing thread.
+ * You should not call this directly; use Thread::bind_thread() instead.
+ */
+void ThreadPosixImpl::
+bind_thread(Thread *thread) {
+  if (_current_thread == nullptr && thread == Thread::get_main_thread()) {
+    _main_thread_known.test_and_set(std::memory_order_relaxed);
+  }
+  _current_thread = thread;
+#ifdef ANDROID
+  bind_java_thread();
+#endif
+}
+
 #ifdef ANDROID
 #ifdef ANDROID
 /**
 /**
  * Attaches the thread to the Java virtual machine.  If this returns true, a
  * Attaches the thread to the Java virtual machine.  If this returns true, a
@@ -247,8 +258,7 @@ root_func(void *data) {
     // TAU_PROFILE("void ThreadPosixImpl::root_func()", " ", TAU_USER);
     // TAU_PROFILE("void ThreadPosixImpl::root_func()", " ", TAU_USER);
 
 
     ThreadPosixImpl *self = (ThreadPosixImpl *)data;
     ThreadPosixImpl *self = (ThreadPosixImpl *)data;
-    int result = pthread_setspecific(_pt_ptr_index, self->_parent_obj);
-    nassertr(result == 0, nullptr);
+    _current_thread = self->_parent_obj;
 
 
     {
     {
       self->_mutex.lock();
       self->_mutex.lock();
@@ -302,27 +312,18 @@ root_func(void *data) {
 }
 }
 
 
 /**
 /**
- * Allocate a new index to store the Thread parent pointer as a piece of per-
- * thread private data.
+ * Called by get_current_thread() if the current therad pointer is null; checks
+ * whether it might be the main thread.
  */
  */
-void ThreadPosixImpl::
-init_pt_ptr_index() {
-  nassertv(!_got_pt_ptr_index);
-
-  int result = pthread_key_create(&_pt_ptr_index, nullptr);
-  if (result != 0) {
-    thread_cat->error()
-      << "Unable to associate Thread pointers with threads.\n";
-    return;
+Thread *ThreadPosixImpl::
+init_current_thread() {
+  Thread *thread = _current_thread;
+  if (!_main_thread_known.test_and_set(std::memory_order_relaxed)) {
+    thread = Thread::get_main_thread();
+    _current_thread = thread;
   }
   }
-
-  _got_pt_ptr_index = true;
-
-  // Assume that we must be in the main thread, since this method must be
-  // called before the first thread is spawned.
-  Thread *main_thread_obj = Thread::get_main_thread();
-  result = pthread_setspecific(_pt_ptr_index, main_thread_obj);
-  nassertv(result == 0);
+  nassertr(thread != nullptr, nullptr);
+  return thread;
 }
 }
 
 
 #ifdef ANDROID
 #ifdef ANDROID

+ 3 - 4
panda/src/pipeline/threadPosixImpl.h

@@ -49,7 +49,7 @@ public:
   INLINE static void prepare_for_exit();
   INLINE static void prepare_for_exit();
 
 
   INLINE static Thread *get_current_thread();
   INLINE static Thread *get_current_thread();
-  INLINE static void bind_thread(Thread *thread);
+  static void bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   INLINE static bool is_threading_supported();
   INLINE static bool is_true_threads();
   INLINE static bool is_true_threads();
   INLINE static bool is_simple_threads();
   INLINE static bool is_simple_threads();
@@ -65,7 +65,7 @@ public:
 
 
 private:
 private:
   static void *root_func(void *data);
   static void *root_func(void *data);
-  static void init_pt_ptr_index();
+  static Thread *init_current_thread();
 
 
   // There appears to be a name collision with the word "Status".
   // There appears to be a name collision with the word "Status".
   enum PStatus {
   enum PStatus {
@@ -86,8 +86,7 @@ private:
   JNIEnv *_jni_env;
   JNIEnv *_jni_env;
 #endif
 #endif
 
 
-  static pthread_key_t _pt_ptr_index;
-  static bool _got_pt_ptr_index;
+  static __thread Thread *_current_thread;
 };
 };
 
 
 #include "threadPosixImpl.I"
 #include "threadPosixImpl.I"

+ 0 - 24
panda/src/pipeline/threadWin32Impl.I

@@ -38,30 +38,6 @@ INLINE void ThreadWin32Impl::
 prepare_for_exit() {
 prepare_for_exit() {
 }
 }
 
 
-/**
- *
- */
-INLINE Thread *ThreadWin32Impl::
-get_current_thread() {
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-  return (Thread *)TlsGetValue(_pt_ptr_index);
-}
-
-/**
- * Associates the indicated Thread object with the currently-executing thread.
- * You should not call this directly; use Thread::bind_thread() instead.
- */
-INLINE void ThreadWin32Impl::
-bind_thread(Thread *thread) {
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-  BOOL result = TlsSetValue(_pt_ptr_index, thread);
-  nassertv(result);
-}
-
 /**
 /**
  *
  *
  */
  */

+ 44 - 32
panda/src/pipeline/threadWin32Impl.cxx

@@ -20,8 +20,28 @@
 #include "pointerTo.h"
 #include "pointerTo.h"
 #include "config_pipeline.h"
 #include "config_pipeline.h"
 
 
-DWORD ThreadWin32Impl::_pt_ptr_index = 0;
-bool ThreadWin32Impl::_got_pt_ptr_index = false;
+static thread_local Thread *_current_thread = nullptr;
+static patomic_flag _main_thread_known = ATOMIC_FLAG_INIT;
+
+/**
+ * Called by get_current_thread() if the current thread pointer is null; checks
+ * whether it might be the main thread.
+ * Note that adding noinline speeds up this call *significantly*, don't remove!
+ */
+static __declspec(noinline) Thread *
+init_current_thread() {
+  Thread *thread = _current_thread;
+  if (!_main_thread_known.test_and_set(std::memory_order_relaxed)) {
+    // Assume that we must be in the main thread, since this method must be
+    // called before the first thread is spawned.
+    thread = Thread::get_main_thread();
+    _current_thread = thread;
+  }
+  // If this assertion triggers, you are making Panda calls from a thread
+  // that has not first been registered using Thread::bind_thread().
+  nassertr(thread != nullptr, nullptr);
+  return thread;
+}
 
 
 /**
 /**
  *
  *
@@ -62,10 +82,6 @@ start(ThreadPriority priority, bool joinable) {
   _joinable = joinable;
   _joinable = joinable;
   _status = S_start_called;
   _status = S_start_called;
 
 
-  if (!_got_pt_ptr_index) {
-    init_pt_ptr_index();
-  }
-
   // Increment the parent object's reference count first.  The thread will
   // Increment the parent object's reference count first.  The thread will
   // eventually decrement it when it terminates.
   // eventually decrement it when it terminates.
   _parent_obj->ref();
   _parent_obj->ref();
@@ -133,6 +149,27 @@ get_unique_id() const {
   return strm.str();
   return strm.str();
 }
 }
 
 
+/**
+ *
+ */
+Thread *ThreadWin32Impl::
+get_current_thread() {
+  Thread *thread = _current_thread;
+  return (thread != nullptr) ? thread : init_current_thread();
+}
+
+/**
+ * Associates the indicated Thread object with the currently-executing thread.
+ * You should not call this directly; use Thread::bind_thread() instead.
+ */
+void ThreadWin32Impl::
+bind_thread(Thread *thread) {
+  if (_current_thread == nullptr && thread == Thread::get_main_thread()) {
+    _main_thread_known.test_and_set(std::memory_order_relaxed);
+  }
+  _current_thread = thread;
+}
+
 /**
 /**
  * The entry point of each thread.
  * The entry point of each thread.
  */
  */
@@ -143,8 +180,7 @@ root_func(LPVOID data) {
     // TAU_PROFILE("void ThreadWin32Impl::root_func()", " ", TAU_USER);
     // TAU_PROFILE("void ThreadWin32Impl::root_func()", " ", TAU_USER);
 
 
     ThreadWin32Impl *self = (ThreadWin32Impl *)data;
     ThreadWin32Impl *self = (ThreadWin32Impl *)data;
-    BOOL result = TlsSetValue(_pt_ptr_index, self->_parent_obj);
-    nassertr(result, 1);
+    _current_thread = self->_parent_obj;
 
 
     {
     {
       self->_mutex.lock();
       self->_mutex.lock();
@@ -185,28 +221,4 @@ root_func(LPVOID data) {
   return 0;
   return 0;
 }
 }
 
 
-/**
- * Allocate a new index to store the Thread parent pointer as a piece of per-
- * thread private data.
- */
-void ThreadWin32Impl::
-init_pt_ptr_index() {
-  nassertv(!_got_pt_ptr_index);
-
-  _pt_ptr_index = TlsAlloc();
-  if (_pt_ptr_index == TLS_OUT_OF_INDEXES) {
-    thread_cat->error()
-      << "Unable to associate Thread pointers with threads.\n";
-    return;
-  }
-
-  _got_pt_ptr_index = true;
-
-  // Assume that we must be in the main thread, since this method must be
-  // called before the first thread is spawned.
-  Thread *main_thread_obj = Thread::get_main_thread();
-  BOOL result = TlsSetValue(_pt_ptr_index, main_thread_obj);
-  nassertv(result);
-}
-
 #endif  // THREAD_WIN32_IMPL
 #endif  // THREAD_WIN32_IMPL

+ 2 - 6
panda/src/pipeline/threadWin32Impl.h

@@ -43,8 +43,8 @@ public:
 
 
   INLINE static void prepare_for_exit();
   INLINE static void prepare_for_exit();
 
 
-  INLINE static Thread *get_current_thread();
-  INLINE static void bind_thread(Thread *thread);
+  static Thread *get_current_thread();
+  static void bind_thread(Thread *thread);
   INLINE static bool is_threading_supported();
   INLINE static bool is_threading_supported();
   INLINE static bool is_true_threads();
   INLINE static bool is_true_threads();
   INLINE static bool is_simple_threads();
   INLINE static bool is_simple_threads();
@@ -54,7 +54,6 @@ public:
 
 
 private:
 private:
   static DWORD WINAPI root_func(LPVOID data);
   static DWORD WINAPI root_func(LPVOID data);
-  static void init_pt_ptr_index();
 
 
   enum Status {
   enum Status {
     S_new,
     S_new,
@@ -70,9 +69,6 @@ private:
   DWORD _thread_id;
   DWORD _thread_id;
   bool _joinable;
   bool _joinable;
   Status _status;
   Status _status;
-
-  static DWORD _pt_ptr_index;
-  static bool _got_pt_ptr_index;
 };
 };
 
 
 #include "threadWin32Impl.I"
 #include "threadWin32Impl.I"

+ 50 - 4
panda/src/pnmimage/pnmBrush.cxx

@@ -142,6 +142,32 @@ public:
   }
   }
 };
 };
 
 
+// Adds a value to the pixel.
+class EXPCL_PANDA_PNMIMAGE PNMAddPixelBrush : public PNMPixelBrush {
+public:
+  PNMAddPixelBrush(const LColorf &color) : PNMPixelBrush(color) { }
+
+  virtual void draw(PNMImage &image, int x, int y, float pixel_scale) {
+    if (x >= 0 && x < image.get_x_size() &&
+        y >= 0 && y < image.get_y_size()) {
+      image.set_xel_a(x, y,
+        image.get_xel_a(x, y) + (_color * pixel_scale));
+    }
+  }
+
+  virtual void fill(PNMImage &image, int xfrom, int xto, int y,
+                    int xo, int yo) {
+    if (y >= 0 && y < image.get_y_size()) {
+      xfrom = max(xfrom, 0);
+      xto = max(xto, image.get_x_size() - 1);
+      for (int x = xfrom; x <= xto; ++x) {
+        image.set_xel_a(x, y,
+          image.get_xel_a(x, y) + _color);
+      }
+    }
+  }
+};
+
 // A PNMImageBrush is a family of brushes that draw an image at a time.
 // A PNMImageBrush is a family of brushes that draw an image at a time.
 class EXPCL_PANDA_PNMIMAGE PNMImageBrush : public PNMBrush {
 class EXPCL_PANDA_PNMIMAGE PNMImageBrush : public PNMBrush {
 protected:
 protected:
@@ -244,6 +270,22 @@ public:
   }
   }
 };
 };
 
 
+// Adds a constant value to the pixels
+class EXPCL_PANDA_PNMIMAGE PNMAddImageBrush : public PNMImageBrush {
+public:
+  PNMAddImageBrush(const PNMImage &image, float xc, float yc) :
+    PNMImageBrush(image, xc, yc) { }
+
+  virtual void draw(PNMImage &image, int x, int y, float pixel_scale) {
+    image.add_sub_image(_image, x, y, 0, 0, -1, -1, pixel_scale);
+  }
+
+  virtual void do_scanline(PNMImage &image, int xto, int yto,
+                           int xfrom, int yfrom, int x_size, int y_size) {
+    image.add_sub_image(_image, xto, yto, xfrom, yfrom, x_size, y_size);
+  }
+};
+
 /**
 /**
  *
  *
  */
  */
@@ -278,6 +320,9 @@ make_pixel(const LColorf &color, PNMBrush::BrushEffect effect) {
 
 
   case BE_lighten:
   case BE_lighten:
     return new PNMLightenPixelBrush(color);
     return new PNMLightenPixelBrush(color);
+
+  case BE_add:
+    return new PNMAddPixelBrush(color);
   }
   }
 
 
   pnmimage_cat.error()
   pnmimage_cat.error()
@@ -296,6 +341,8 @@ make_spot(const LColorf &color, float radius, bool fuzzy,
 
 
   switch (effect) {
   switch (effect) {
   case BE_set:
   case BE_set:
+  case BE_lighten:
+  case BE_add:
     bg.set(0, 0, 0, 0);
     bg.set(0, 0, 0, 0);
     break;
     break;
 
 
@@ -307,10 +354,6 @@ make_spot(const LColorf &color, float radius, bool fuzzy,
     bg.set(1, 1, 1, 1);
     bg.set(1, 1, 1, 1);
     break;
     break;
 
 
-  case BE_lighten:
-    bg.set(0, 0, 0, 0);
-    break;
-
   default:
   default:
     pnmimage_cat.error()
     pnmimage_cat.error()
       << "**Invalid BrushEffect (" << (int)effect << ")**\n";
       << "**Invalid BrushEffect (" << (int)effect << ")**\n";
@@ -351,6 +394,9 @@ make_image(const PNMImage &image, float xc, float yc,
 
 
   case BE_lighten:
   case BE_lighten:
     return new PNMLightenImageBrush(image, xc, yc);
     return new PNMLightenImageBrush(image, xc, yc);
+
+  case BE_add:
+    return new PNMAddImageBrush(image, xc, yc);
   }
   }
 
 
   pnmimage_cat.error()
   pnmimage_cat.error()

+ 1 - 0
panda/src/pnmimage/pnmBrush.h

@@ -45,6 +45,7 @@ PUBLISHED:
     BE_blend,
     BE_blend,
     BE_darken,
     BE_darken,
     BE_lighten,
     BE_lighten,
+    BE_add,
   };
   };
 
 
   static PT(PNMBrush) make_transparent();
   static PT(PNMBrush) make_transparent();

+ 3 - 2
panda/src/pnmimage/pnmImage.cxx

@@ -1980,13 +1980,14 @@ quantize(size_t max_colors) {
  * PerlinNoise2 class in mathutil.
  * PerlinNoise2 class in mathutil.
  */
  */
 void PNMImage::
 void PNMImage::
-perlin_noise_fill(float sx, float sy, int table_size, unsigned long seed) {
+perlin_noise_fill(float sx, float sy, int table_size, unsigned long seed,
+                  float ox, float oy) {
   float x, y;
   float x, y;
   float noise;
   float noise;
   PerlinNoise2 perlin (sx * _x_size, sy * _y_size, table_size, seed);
   PerlinNoise2 perlin (sx * _x_size, sy * _y_size, table_size, seed);
   for (x = 0; x < _x_size; ++x) {
   for (x = 0; x < _x_size; ++x) {
     for (y = 0; y < _y_size; ++y) {
     for (y = 0; y < _y_size; ++y) {
-      noise = perlin.noise(x, y);
+      noise = perlin.noise(x + ox, y + oy);
       set_xel(x, y, 0.5 * (noise + 1.0));
       set_xel(x, y, 0.5 * (noise + 1.0));
     }
     }
   }
   }

+ 2 - 1
panda/src/pnmimage/pnmImage.h

@@ -258,7 +258,8 @@ PUBLISHED:
   void make_histogram(Histogram &hist);
   void make_histogram(Histogram &hist);
   void quantize(size_t max_colors);
   void quantize(size_t max_colors);
   BLOCKING void perlin_noise_fill(float sx, float sy, int table_size = 256,
   BLOCKING void perlin_noise_fill(float sx, float sy, int table_size = 256,
-                                  unsigned long seed = 0);
+                                  unsigned long seed = 0,
+                                  float ox = 0, float oy = 0);
   void perlin_noise_fill(StackedPerlinNoise2 &perlin);
   void perlin_noise_fill(StackedPerlinNoise2 &perlin);
 
 
   void remix_channels(const LMatrix4 &conv);
   void remix_channels(const LMatrix4 &conv);

+ 6 - 0
panda/src/pstatclient/pStatClientControlMessage.cxx

@@ -39,6 +39,7 @@ encode(Datagram &datagram) const {
     datagram.add_string(_client_progname);
     datagram.add_string(_client_progname);
     datagram.add_uint16(_major_version);
     datagram.add_uint16(_major_version);
     datagram.add_uint16(_minor_version);
     datagram.add_uint16(_minor_version);
+    datagram.add_uint32(_client_pid);
     break;
     break;
 
 
   case T_define_collectors:
   case T_define_collectors:
@@ -86,6 +87,11 @@ decode(const Datagram &datagram, PStatClientVersion *version) {
       _major_version = source.get_uint16();
       _major_version = source.get_uint16();
       _minor_version = source.get_uint16();
       _minor_version = source.get_uint16();
     }
     }
+    if (source.get_remaining_size() >= 4) {
+      _client_pid = source.get_uint32();
+    } else {
+      _client_pid = -1;
+    }
     break;
     break;
 
 
   case T_define_collectors:
   case T_define_collectors:

+ 1 - 0
panda/src/pstatclient/pStatClientControlMessage.h

@@ -47,6 +47,7 @@ public:
   // Used for T_hello
   // Used for T_hello
   std::string _client_hostname;
   std::string _client_hostname;
   std::string _client_progname;
   std::string _client_progname;
+  int _client_pid;
   int _major_version;
   int _major_version;
   int _minor_version;
   int _minor_version;
 
 

+ 7 - 1
panda/src/pstatclient/pStatClientImpl.cxx

@@ -158,6 +158,8 @@ client_disconnect() {
  */
  */
 void PStatClientImpl::
 void PStatClientImpl::
 new_frame(int thread_index) {
 new_frame(int thread_index) {
+  double frame_start = get_real_time();
+
   nassertv(thread_index >= 0 && thread_index < _client->_num_threads);
   nassertv(thread_index >= 0 && thread_index < _client->_num_threads);
 
 
   PStatClient::InternalThread *pthread = _client->get_thread_ptr(thread_index);
   PStatClient::InternalThread *pthread = _client->get_thread_ptr(thread_index);
@@ -178,7 +180,6 @@ new_frame(int thread_index) {
     return;
     return;
   }
   }
 
 
-  double frame_start = get_real_time();
   int frame_number = -1;
   int frame_number = -1;
   PStatFrameData frame_data;
   PStatFrameData frame_data;
 
 
@@ -396,6 +397,11 @@ send_hello() {
   message._type = PStatClientControlMessage::T_hello;
   message._type = PStatClientControlMessage::T_hello;
   message._client_hostname = get_hostname();
   message._client_hostname = get_hostname();
   message._client_progname = _client_name;
   message._client_progname = _client_name;
+#ifdef _WIN32
+  message._client_pid = GetCurrentProcessId();
+#else
+  message._client_pid = getpid();
+#endif
   message._major_version = get_current_pstat_major_version();
   message._major_version = get_current_pstat_major_version();
   message._minor_version = get_current_pstat_minor_version();
   message._minor_version = get_current_pstat_minor_version();
 
 

+ 1 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -109,6 +109,7 @@ static TimeCollectorProperties time_properties[] = {
   { 1, "App:Collisions:Reset",             { 0.0, 0.0, 0.5 } },
   { 1, "App:Collisions:Reset",             { 0.0, 0.0, 0.5 } },
   { 0, "App:Data graph",                   { 0.5, 0.8, 0.4 } },
   { 0, "App:Data graph",                   { 0.5, 0.8, 0.4 } },
   { 1, "App:Show code",                    { 0.8, 0.2, 1.0 } },
   { 1, "App:Show code",                    { 0.8, 0.2, 1.0 } },
+  { 0, "App:Show code:General",            { 0.4, 0.3, 0.9 } },
   { 0, "App:Show code:Nametags",           { 0.8, 0.8, 1.0 } },
   { 0, "App:Show code:Nametags",           { 0.8, 0.8, 1.0 } },
   { 0, "App:Show code:Nametags:2d",        { 0.0, 0.0, 0.5 } },
   { 0, "App:Show code:Nametags:2d",        { 0.0, 0.0, 0.5 } },
   { 0, "App:Show code:Nametags:2d:Contents", { 0.0, 0.5, 0.0 } },
   { 0, "App:Show code:Nametags:2d:Contents", { 0.0, 0.5, 0.0 } },

+ 47 - 10
panda/src/windisplay/winGraphicsWindow.cxx

@@ -33,6 +33,10 @@
 #define WM_TOUCH 0x0240
 #define WM_TOUCH 0x0240
 #endif
 #endif
 
 
+#ifndef WM_MOUSEHWHEEL
+#define WM_MOUSEHWHEEL 0x020E
+#endif
+
 #if WINVER < 0x0601
 #if WINVER < 0x0601
 // Not used on Windows XP, but we still need to define it.
 // Not used on Windows XP, but we still need to define it.
 #define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
 #define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
@@ -283,6 +287,17 @@ process_events() {
  */
  */
 void WinGraphicsWindow::
 void WinGraphicsWindow::
 set_properties_now(WindowProperties &properties) {
 set_properties_now(WindowProperties &properties) {
+  if (properties.has_fullscreen() && !properties.get_fullscreen() &&
+      is_fullscreen()) {
+    if (do_windowed_switch()) {
+      _properties.set_fullscreen(false);
+      properties.clear_fullscreen();
+    } else {
+      windisplay_cat.warning()
+        << "Switching to windowed mode failed!\n";
+    }
+  }
+
   GraphicsWindow::set_properties_now(properties);
   GraphicsWindow::set_properties_now(properties);
   if (!properties.is_any_specified()) {
   if (!properties.is_any_specified()) {
     // The base class has already handled this case.
     // The base class has already handled this case.
@@ -437,14 +452,6 @@ set_properties_now(WindowProperties &properties) {
         windisplay_cat.warning()
         windisplay_cat.warning()
           << "Switching to fullscreen mode failed!\n";
           << "Switching to fullscreen mode failed!\n";
       }
       }
-    } else if (!properties.get_fullscreen() && is_fullscreen()){
-      if (do_windowed_switch()){
-        _properties.set_fullscreen(false);
-        properties.clear_fullscreen();
-      } else {
-        windisplay_cat.warning()
-          << "Switching to windowed mode failed!\n";
-      }
     }
     }
   }
   }
 
 
@@ -1808,6 +1815,31 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
     }
     }
     break;
     break;
 
 
+  case WM_MOUSEHWHEEL:
+    {
+      int delta = GET_WHEEL_DELTA_WPARAM(wparam);
+
+      POINT point;
+      GetCursorPos(&point);
+      ScreenToClient(hwnd, &point);
+      double time = get_message_time();
+
+      if (delta >= 0) {
+        while (delta > 0) {
+          handle_keypress(MouseButton::wheel_right(), point.x, point.y, time);
+          handle_keyrelease(MouseButton::wheel_right(), time);
+          delta -= WHEEL_DELTA;
+        }
+      } else {
+        while (delta < 0) {
+          handle_keypress(MouseButton::wheel_left(), point.x, point.y, time);
+          handle_keyrelease(MouseButton::wheel_left(), time);
+          delta += WHEEL_DELTA;
+        }
+      }
+      return 0;
+    }
+    break;
 
 
   case WM_IME_SETCONTEXT:
   case WM_IME_SETCONTEXT:
     if (!ime_hide)
     if (!ime_hide)
@@ -2651,6 +2683,10 @@ lookup_key(WPARAM wparam) const {
   case VK_LMENU: return KeyboardButton::lalt();
   case VK_LMENU: return KeyboardButton::lalt();
   case VK_RMENU: return KeyboardButton::ralt();
   case VK_RMENU: return KeyboardButton::ralt();
 
 
+  case VK_LWIN: return KeyboardButton::lmeta();
+  case VK_RWIN: return KeyboardButton::rmeta();
+  case VK_APPS: return KeyboardButton::menu();
+
   default:
   default:
     int key = MapVirtualKey(wparam, 2);
     int key = MapVirtualKey(wparam, 2);
     if (isascii(key) && key != 0) {
     if (isascii(key) && key != 0) {
@@ -2796,6 +2832,7 @@ lookup_raw_key(LPARAM lparam) const {
 
 
   // A few additional keys don't fit well in the above table.
   // A few additional keys don't fit well in the above table.
   switch (vsc) {
   switch (vsc) {
+  case 86: return KeyboardButton::ascii_key('<'); // Between lshift and z
   case 87: return KeyboardButton::f11();
   case 87: return KeyboardButton::f11();
   case 88: return KeyboardButton::f12();
   case 88: return KeyboardButton::f12();
   default: return ButtonHandle::none();
   default: return ButtonHandle::none();
@@ -2815,10 +2852,10 @@ get_keyboard_map() const {
 
 
   wchar_t text[256];
   wchar_t text[256];
   UINT vsc = 0;
   UINT vsc = 0;
-  unsigned short ex_vsc[] = {0x57, 0x58,
+  unsigned short ex_vsc[] = {0x56, 0x57, 0x58,
     0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
     0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
 
 
-  for (int k = 1; k < 84 + 17; ++k) {
+  for (int k = 1; k < 84 + sizeof(ex_vsc) / sizeof(short); ++k) {
     if (k >= 84) {
     if (k >= 84) {
       vsc = ex_vsc[k - 84];
       vsc = ex_vsc[k - 84];
     } else {
     } else {

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

@@ -2230,6 +2230,8 @@ get_mouse_button(XButtonEvent &button_event) {
     return MouseButton::wheel_left();
     return MouseButton::wheel_left();
   } else if (index == x_wheel_right_button) {
   } else if (index == x_wheel_right_button) {
     return MouseButton::wheel_right();
     return MouseButton::wheel_right();
+  } else if (index >= 8) {
+    return MouseButton::button(index - 5);
   } else {
   } else {
     return MouseButton::button(index - 1);
     return MouseButton::button(index - 1);
   }
   }

+ 4 - 2
pandatool/src/gtk-stats/CMakeLists.txt

@@ -1,9 +1,10 @@
-if(NOT HAVE_GTK2 OR NOT HAVE_NET)
+if(NOT HAVE_GTK3 OR NOT HAVE_NET)
   return()
   return()
 endif()
 endif()
 
 
 set(GTKSTATS_HEADERS
 set(GTKSTATS_HEADERS
   gtkStatsChartMenu.h
   gtkStatsChartMenu.h
+  gtkStatsFlameGraph.h
   gtkStatsGraph.h
   gtkStatsGraph.h
   gtkStatsLabel.h
   gtkStatsLabel.h
   gtkStatsLabelStack.h
   gtkStatsLabelStack.h
@@ -17,6 +18,7 @@ set(GTKSTATS_HEADERS
 set(GTKSTATS_SOURCES
 set(GTKSTATS_SOURCES
   gtkStats.cxx
   gtkStats.cxx
   gtkStatsChartMenu.cxx
   gtkStatsChartMenu.cxx
+  gtkStatsFlameGraph.cxx
   gtkStatsGraph.cxx
   gtkStatsGraph.cxx
   gtkStatsLabel.cxx
   gtkStatsLabel.cxx
   gtkStatsLabelStack.cxx
   gtkStatsLabelStack.cxx
@@ -28,7 +30,7 @@ set(GTKSTATS_SOURCES
 
 
 composite_sources(gtk-stats GTKSTATS_SOURCES)
 composite_sources(gtk-stats GTKSTATS_SOURCES)
 add_executable(gtk-stats ${GTKSTATS_HEADERS} ${GTKSTATS_SOURCES})
 add_executable(gtk-stats ${GTKSTATS_HEADERS} ${GTKSTATS_SOURCES})
-target_link_libraries(gtk-stats p3progbase p3pstatserver PKG::GTK2)
+target_link_libraries(gtk-stats p3progbase p3pstatserver PKG::GTK3)
 
 
 # This program is NOT actually called gtk-stats. It's pstats-gtk on Win32 and
 # This program is NOT actually called gtk-stats. It's pstats-gtk on Win32 and
 # pstats everywhere else (as the Win32 GUI is not built).
 # pstats everywhere else (as the Win32 GUI is not built).

+ 1 - 1
pandatool/src/gtk-stats/gtkStats.cxx

@@ -44,7 +44,7 @@ timer(gpointer data) {
     // are getting starved and falling behind, so that the user still gets a
     // are getting starved and falling behind, so that the user still gets a
     // chance to see *something* happen onscreen, even if it's just
     // chance to see *something* happen onscreen, even if it's just
     // increasingly old data.
     // increasingly old data.
-    gdk_window_process_all_updates();
+    //gdk_window_process_all_updates();
   }
   }
 
 
   return TRUE;
   return TRUE;

+ 32 - 14
pandatool/src/gtk-stats/gtkStatsChartMenu.cxx

@@ -104,9 +104,9 @@ do_update() {
       // We put a separator between the above frame collector and the first
       // We put a separator between the above frame collector and the first
       // level collector.
       // level collector.
       if (needs_separator) {
       if (needs_separator) {
-  GtkWidget *sep = gtk_separator_menu_item_new();
-  gtk_widget_show(sep);
-  gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep);
+        GtkWidget *sep = gtk_separator_menu_item_new();
+        gtk_widget_show(sep);
+        gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep);
 
 
         needs_separator = false;
         needs_separator = false;
       }
       }
@@ -116,20 +116,34 @@ do_update() {
     }
     }
   }
   }
 
 
-  // Also a menu item for a piano roll (following a separator).
+  // Also menu items for flame graph and piano roll (following a separator).
   GtkWidget *sep = gtk_separator_menu_item_new();
   GtkWidget *sep = gtk_separator_menu_item_new();
   gtk_widget_show(sep);
   gtk_widget_show(sep);
   gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep);
   gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep);
 
 
-  GtkStatsMonitor::MenuDef smd(_thread_index, -1, false);
-  const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd);
+  {
+    GtkStatsMonitor::MenuDef smd(_thread_index, -2, false);
+    const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd);
 
 
-  GtkWidget *menu_item = gtk_menu_item_new_with_label("Piano Roll");
-  gtk_widget_show(menu_item);
-  gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item);
+    GtkWidget *menu_item = gtk_menu_item_new_with_label("Flame Graph");
+    gtk_widget_show(menu_item);
+    gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item);
 
 
-  g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
-         G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
+    g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
+           G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
+  }
+
+  {
+    GtkStatsMonitor::MenuDef smd(_thread_index, -1, false);
+    const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd);
+
+    GtkWidget *menu_item = gtk_menu_item_new_with_label("Piano Roll");
+    gtk_widget_show(menu_item);
+    gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item);
+
+    g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
+           G_CALLBACK(handle_menu), (void *)(const void *)menu_def);
+  }
 }
 }
 
 
 /**
 /**
@@ -138,7 +152,7 @@ do_update() {
  */
  */
 void GtkStatsChartMenu::
 void GtkStatsChartMenu::
 add_view(GtkWidget *parent_menu, const PStatViewLevel *view_level,
 add_view(GtkWidget *parent_menu, const PStatViewLevel *view_level,
-   bool show_level) {
+         bool show_level) {
   int collector = view_level->get_collector();
   int collector = view_level->get_collector();
 
 
   const PStatClientData *client_data = _monitor->get_client_data();
   const PStatClientData *client_data = _monitor->get_client_data();
@@ -189,9 +203,13 @@ handle_menu(gpointer data) {
     return;
     return;
   }
   }
 
 
-  if (menu_def->_collector_index < 0) {
+  if (menu_def->_collector_index == -2) {
+    monitor->open_flame_graph(menu_def->_thread_index);
+  }
+  else if (menu_def->_collector_index < 0) {
     monitor->open_piano_roll(menu_def->_thread_index);
     monitor->open_piano_roll(menu_def->_thread_index);
-  } else {
+  }
+  else {
     monitor->open_strip_chart(menu_def->_thread_index,
     monitor->open_strip_chart(menu_def->_thread_index,
             menu_def->_collector_index,
             menu_def->_collector_index,
             menu_def->_show_level);
             menu_def->_show_level);

+ 551 - 0
pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx

@@ -0,0 +1,551 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file gtkStatsFlameGraph.cxx
+ * @author rdb
+ * @date 2022-02-02
+ */
+
+#include "gtkStatsFlameGraph.h"
+#include "gtkStatsLabel.h"
+#include "gtkStatsMonitor.h"
+#include "pStatCollectorDef.h"
+
+static const int default_flame_graph_width = 800;
+static const int default_flame_graph_height = 150;
+
+/**
+ *
+ */
+GtkStatsFlameGraph::
+GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index,
+                   int collector_index) :
+  PStatFlameGraph(monitor, monitor->get_view(thread_index),
+                  thread_index, collector_index,
+                  default_flame_graph_width,
+                  default_flame_graph_height),
+  GtkStatsGraph(monitor)
+{
+  // Let's show the units on the guide bar labels.  There's room.
+  set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
+
+  // Put some stuff on top of the graph.
+  _top_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_box_pack_start(GTK_BOX(_graph_vbox), _top_hbox,
+         FALSE, FALSE, 0);
+
+  _average_check_box = gtk_check_button_new_with_label("Average");
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_average_check_box), TRUE);
+  g_signal_connect(G_OBJECT(_average_check_box), "toggled",
+       G_CALLBACK(toggled_callback), this);
+
+  // Add a DrawingArea widget on top of the graph, to display all of the scale
+  // units.
+  _scale_area = gtk_drawing_area_new();
+  g_signal_connect(G_OBJECT(_scale_area), "draw", G_CALLBACK(draw_callback), this);
+
+  _total_label = gtk_label_new("");
+  gtk_box_pack_start(GTK_BOX(_top_hbox), _average_check_box, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(_top_hbox), _scale_area, TRUE, TRUE, 0);
+  gtk_box_pack_end(GTK_BOX(_top_hbox), _total_label, FALSE, FALSE, 0);
+
+  gtk_widget_set_size_request(_graph_window, default_flame_graph_width,
+                              default_flame_graph_height);
+
+  // Add a fixed container to the overlay to allow arbitrary positioning
+  // of labels therein.
+  _fixed = gtk_fixed_new();
+  gtk_overlay_add_overlay(GTK_OVERLAY(_graph_overlay), _fixed);
+
+  gtk_widget_show_all(_window);
+  gtk_widget_show(_window);
+
+  // Allow the window to be resized as small as the user likes.  We have to do
+  // this after the window has been shown; otherwise, it will affect the
+  // window's initial size.
+  gtk_widget_set_size_request(_window, 0, 0);
+
+  clear_region();
+}
+
+/**
+ *
+ */
+GtkStatsFlameGraph::
+~GtkStatsFlameGraph() {
+}
+
+/**
+ * Called whenever a new Collector definition is received from the client.
+ */
+void GtkStatsFlameGraph::
+new_collector(int collector_index) {
+  GtkStatsGraph::new_collector(collector_index);
+}
+
+/**
+ * Called as each frame's data is made available.  There is no guarantee the
+ * frames will arrive in order, or that all of them will arrive at all.  The
+ * monitor should be prepared to accept frames received out-of-order or
+ * missing.
+ */
+void GtkStatsFlameGraph::
+new_data(int thread_index, int frame_number) {
+  if (is_title_unknown()) {
+    std::string window_title = get_title_text();
+    if (!is_title_unknown()) {
+      gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str());
+    }
+  }
+
+  if (!_pause) {
+    update();
+
+    std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
+    if (_net_value_text != text) {
+      _net_value_text = text;
+      gtk_label_set_text(GTK_LABEL(_total_label), _net_value_text.c_str());
+    }
+  }
+}
+
+/**
+ * Called when it is necessary to redraw the entire graph.
+ */
+void GtkStatsFlameGraph::
+force_redraw() {
+  PStatFlameGraph::force_redraw();
+}
+
+/**
+ * Called when the user has resized the window, forcing a resize of the graph.
+ */
+void GtkStatsFlameGraph::
+changed_graph_size(int graph_xsize, int graph_ysize) {
+  PStatFlameGraph::changed_size(graph_xsize, graph_ysize);
+}
+
+/**
+ * Called when the user selects a new time units from the monitor pulldown
+ * menu, this should adjust the units for the graph to the indicated mask if
+ * it is a time-based graph.
+ */
+void GtkStatsFlameGraph::
+set_time_units(int unit_mask) {
+  int old_unit_mask = get_guide_bar_units();
+  if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
+    unit_mask = unit_mask & (GBU_hz | GBU_ms);
+    unit_mask |= (old_unit_mask & GBU_show_units);
+    set_guide_bar_units(unit_mask);
+
+    gtk_widget_queue_draw(_scale_area);
+  }
+}
+
+/**
+ * Called when the user single-clicks on a label.
+ */
+void GtkStatsFlameGraph::
+on_click_label(int collector_index) {
+  int prev_collector_index = get_collector_index();
+  if (collector_index == prev_collector_index && collector_index != 0) {
+    // Clicking on the top label means to go up to the parent level.
+    const PStatClientData *client_data =
+      GtkStatsGraph::_monitor->get_client_data();
+    if (client_data->has_collector(collector_index)) {
+      const PStatCollectorDef &def =
+        client_data->get_collector_def(collector_index);
+      collector_index = def._parent_index;
+      set_collector_index(collector_index);
+    }
+  }
+  else {
+    // Clicking on any other label means to focus on that.
+    set_collector_index(collector_index);
+  }
+
+  // Change the root collector to show the full name.
+  if (prev_collector_index != collector_index) {
+    auto it = _labels.find(prev_collector_index);
+    if (it != _labels.end()) {
+      it->second->update_text(false);
+    }
+    it = _labels.find(collector_index);
+    if (it != _labels.end()) {
+      it->second->update_text(true);
+    }
+  }
+}
+
+/**
+ * Called when the user hovers the mouse over a label.
+ */
+void GtkStatsFlameGraph::
+on_enter_label(int collector_index) {
+  if (collector_index != _highlighted_index) {
+    _highlighted_index = collector_index;
+  }
+}
+
+/**
+ * Called when the user's mouse cursor leaves a label.
+ */
+void GtkStatsFlameGraph::
+on_leave_label(int collector_index) {
+  if (collector_index == _highlighted_index && collector_index != -1) {
+    _highlighted_index = -1;
+  }
+}
+
+/**
+ * Called when the mouse hovers over a label, and should return the text that
+ * should appear on the tooltip.
+ */
+std::string GtkStatsFlameGraph::
+get_label_tooltip(int collector_index) const {
+  return PStatFlameGraph::get_label_tooltip(collector_index);
+}
+
+/**
+ * Repositions the labels.
+ */
+void GtkStatsFlameGraph::
+update_labels() {
+  PStatFlameGraph::update_labels();
+}
+
+/**
+ * Repositions a label.  If width is 0, the label should be deleted.
+ */
+void GtkStatsFlameGraph::
+update_label(int collector_index, int row, int x, int width) {
+  GtkStatsLabel *label;
+
+  auto it = _labels.find(collector_index);
+  if (it != _labels.end()) {
+    label = it->second;
+    if (width == 0) {
+      gtk_container_remove(GTK_CONTAINER(_fixed), label->get_widget());
+      delete label;
+      _labels.erase(it);
+      return;
+    }
+    gtk_fixed_move(GTK_FIXED(_fixed), label->get_widget(), x, _ysize - (row + 1) * label->get_height());
+  }
+  else {
+    if (width == 0) {
+      return;
+    }
+    label = new GtkStatsLabel(GtkStatsGraph::_monitor, this, _thread_index, collector_index, false, false);
+    _labels[collector_index] = label;
+    gtk_fixed_put(GTK_FIXED(_fixed), label->get_widget(), x, _ysize - (row + 1) * label->get_height());
+  }
+
+  gtk_widget_set_size_request(label->get_widget(), std::min(width, _xsize), label->get_height());
+}
+
+/**
+ * Calls update_guide_bars with parameters suitable to this kind of graph.
+ */
+void GtkStatsFlameGraph::
+normal_guide_bars() {
+  // We want vaguely 100 pixels between guide bars.
+  double res = gdk_screen_get_resolution(gdk_screen_get_default());
+  int num_bars = (int)(get_xsize() / (100.0 * (res > 0 ? res / 96.0 : 1.0)));
+
+  _guide_bars.clear();
+
+  double dist = get_horizontal_scale() / num_bars;
+
+  for (int i = 1; i < num_bars; ++i) {
+    _guide_bars.push_back(make_guide_bar(i * dist));
+  }
+
+  _guide_bars_changed = true;
+}
+
+/**
+ * Erases the chart area.
+ */
+void GtkStatsFlameGraph::
+clear_region() {
+  cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0);
+  cairo_paint(_cr);
+}
+
+/**
+ * Erases the chart area in preparation for drawing a bunch of bars.
+ */
+void GtkStatsFlameGraph::
+begin_draw() {
+  clear_region();
+
+  // Draw in the guide bars.
+  int num_guide_bars = get_num_guide_bars();
+  for (int i = 0; i < num_guide_bars; i++) {
+    draw_guide_bar(_cr, get_guide_bar(i));
+  }
+}
+
+/**
+ * Called after all the bars have been drawn, this triggers a refresh event to
+ * draw it to the window.
+ */
+void GtkStatsFlameGraph::
+end_draw() {
+  gtk_widget_queue_draw(_graph_window);
+}
+
+/**
+ * Called at the end of the draw cycle.
+ */
+void GtkStatsFlameGraph::
+idle() {
+}
+
+/**
+ * This is called during the servicing of the draw event; it gives a derived
+ * class opportunity to do some further painting into the graph window.
+ */
+void GtkStatsFlameGraph::
+additional_graph_window_paint(cairo_t *cr) {
+  int num_user_guide_bars = get_num_user_guide_bars();
+  for (int i = 0; i < num_user_guide_bars; i++) {
+    draw_guide_bar(cr, get_user_guide_bar(i));
+  }
+}
+
+/**
+ * Based on the mouse position within the window's client area, look for
+ * draggable things the mouse might be hovering over and return the
+ * apprioprate DragMode enum or DM_none if nothing is indicated.
+ */
+GtkStatsGraph::DragMode GtkStatsFlameGraph::
+consider_drag_start(int graph_x, int graph_y) {
+  if (graph_y >= 0 && graph_y < get_ysize()) {
+    if (graph_x >= 0 && graph_x < get_xsize()) {
+      // See if the mouse is over a user-defined guide bar.
+      int x = graph_x;
+      double from_height = pixel_to_height(x - 2);
+      double to_height = pixel_to_height(x + 2);
+      _drag_guide_bar = find_user_guide_bar(from_height, to_height);
+      if (_drag_guide_bar >= 0) {
+        return DM_guide_bar;
+      }
+
+    } else {
+      // The mouse is left or right of the graph; maybe create a new guide
+      // bar.
+      return DM_new_guide_bar;
+    }
+  }
+
+  return DM_none;
+}
+
+/**
+ * Called when the mouse button is depressed within the graph window.
+ */
+gboolean GtkStatsFlameGraph::
+handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
+        bool double_click) {
+  if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) {
+    if (double_click) {
+      // Clicking on whitespace in the graph goes to the parent.
+      on_click_label(get_collector_index());
+      return TRUE;
+    }
+  }
+
+  if (_potential_drag_mode == DM_none) {
+    set_drag_mode(DM_scale);
+    _drag_scale_start = pixel_to_height(graph_x);
+    // SetCapture(_graph_window);
+    return TRUE;
+
+  } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
+    set_drag_mode(DM_guide_bar);
+    _drag_start_x = graph_x;
+    // SetCapture(_graph_window);
+    return TRUE;
+  }
+
+  return GtkStatsGraph::handle_button_press(widget, graph_x, graph_y,
+              double_click);
+}
+
+/**
+ * Called when the mouse button is released within the graph window.
+ */
+gboolean GtkStatsFlameGraph::
+handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_drag_mode == DM_scale) {
+    set_drag_mode(DM_none);
+    // ReleaseCapture();
+    return handle_motion(widget, graph_x, graph_y);
+
+  } else if (_drag_mode == DM_guide_bar) {
+    if (graph_x < 0 || graph_x >= get_xsize()) {
+      remove_user_guide_bar(_drag_guide_bar);
+    } else {
+      move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
+    }
+    set_drag_mode(DM_none);
+    // ReleaseCapture();
+    return handle_motion(widget, graph_x, graph_y);
+  }
+
+  return GtkStatsGraph::handle_button_release(widget, graph_x, graph_y);
+}
+
+/**
+ * Called when the mouse is moved within the graph window.
+ */
+gboolean GtkStatsFlameGraph::
+handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_drag_mode == DM_new_guide_bar) {
+    // We haven't created the new guide bar yet; we won't until the mouse
+    // comes within the graph's region.
+    if (graph_x >= 0 && graph_x < get_xsize()) {
+      set_drag_mode(DM_guide_bar);
+      _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x));
+      return TRUE;
+    }
+  }
+  else if (_drag_mode == DM_guide_bar) {
+    move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
+    return TRUE;
+  }
+
+  return GtkStatsGraph::handle_motion(widget, graph_x, graph_y);
+}
+
+/**
+ * Draws the line for the indicated guide bar on the graph.
+ */
+void GtkStatsFlameGraph::
+draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar) {
+  int x = height_to_pixel(bar._height);
+
+  if (x > 0 && x < get_xsize() - 1) {
+    // Only draw it if it's not too close to the top.
+    switch (bar._style) {
+    case GBS_target:
+      cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]);
+      break;
+
+    case GBS_user:
+      cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]);
+      break;
+
+    case GBS_normal:
+      cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]);
+      break;
+    }
+    cairo_move_to(cr, x, 0);
+    cairo_line_to(cr, x, get_ysize());
+    cairo_stroke(cr);
+  }
+}
+
+/**
+ * This is called during the servicing of the draw event.
+ */
+void GtkStatsFlameGraph::
+draw_guide_labels(cairo_t *cr) {
+  int i;
+  int num_guide_bars = get_num_guide_bars();
+  for (i = 0; i < num_guide_bars; i++) {
+    draw_guide_label(cr, get_guide_bar(i));
+  }
+
+  int num_user_guide_bars = get_num_user_guide_bars();
+  for (i = 0; i < num_user_guide_bars; i++) {
+    draw_guide_label(cr, get_user_guide_bar(i));
+  }
+}
+
+/**
+ * Draws the text for the indicated guide bar label at the top of the graph.
+ */
+void GtkStatsFlameGraph::
+draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar) {
+  switch (bar._style) {
+  case GBS_target:
+    cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]);
+    break;
+
+  case GBS_user:
+    cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]);
+    break;
+
+  case GBS_normal:
+    cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]);
+    break;
+  }
+
+  int x = height_to_pixel(bar._height);
+  const std::string &label = bar._label;
+
+  PangoLayout *layout = gtk_widget_create_pango_layout(_window, label.c_str());
+  int width, height;
+  pango_layout_get_pixel_size(layout, &width, &height);
+
+  if (bar._style != GBS_user) {
+    double from_height = pixel_to_height(x - width);
+    double to_height = pixel_to_height(x + width);
+    if (find_user_guide_bar(from_height, to_height) >= 0) {
+      // Omit the label: there's a user-defined guide bar in the same space.
+      g_object_unref(layout);
+      return;
+    }
+  }
+
+  if (x >= 0 && x < get_xsize()) {
+    // Now convert our x to a coordinate within our drawing area.
+    int junk_y;
+
+    // The x coordinate comes from the graph_window.
+    gtk_widget_translate_coordinates(_graph_window, _scale_area,
+             x, 0,
+             &x, &junk_y);
+
+    GtkAllocation allocation;
+    gtk_widget_get_allocation(_scale_area, &allocation);
+
+    int this_x = x - width / 2;
+    if (this_x >= 0 && this_x + width < allocation.width) {
+      cairo_move_to(cr, this_x, allocation.height - height);
+      pango_cairo_show_layout(cr, layout);
+    }
+  }
+
+  g_object_unref(layout);
+}
+
+/**
+ * Called when the average check box is toggled.
+ */
+void GtkStatsFlameGraph::
+toggled_callback(GtkToggleButton *button, gpointer data) {
+  GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data;
+
+  bool active = gtk_toggle_button_get_active(button);
+  self->set_average_mode(active);
+}
+
+/**
+ * Draws in the scale labels.
+ */
+gboolean GtkStatsFlameGraph::
+draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) {
+  GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data;
+  self->draw_guide_labels(cr);
+
+  return TRUE;
+}

+ 81 - 0
pandatool/src/gtk-stats/gtkStatsFlameGraph.h

@@ -0,0 +1,81 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file gtkStatsFlameGraph.h
+ * @author rdb
+ * @date 2022-02-02
+ */
+
+#ifndef GTKSTATSFLAMEGRAPH_H
+#define GTKSTATSFLAMEGRAPH_H
+
+#include "pandatoolbase.h"
+
+#include "gtkStatsGraph.h"
+#include "pStatFlameGraph.h"
+
+class GtkStatsLabel;
+
+/**
+ * A window that draws a flame chart, which shows the collectors explicitly
+ * stopping and starting, one frame at a time.
+ */
+class GtkStatsFlameGraph : public PStatFlameGraph, public GtkStatsGraph {
+public:
+  GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index,
+                     int collector_index=0);
+  virtual ~GtkStatsFlameGraph();
+
+  virtual void new_collector(int collector_index);
+  virtual void new_data(int thread_index, int frame_number);
+  virtual void force_redraw();
+  virtual void changed_graph_size(int graph_xsize, int graph_ysize);
+
+  virtual void set_time_units(int unit_mask);
+  virtual void on_click_label(int collector_index);
+  virtual void on_enter_label(int collector_index);
+  virtual void on_leave_label(int collector_index);
+  virtual std::string get_label_tooltip(int collector_index) const;
+
+protected:
+  virtual void update_labels();
+  virtual void update_label(int collector_index, int row, int x, int width);
+  virtual void normal_guide_bars();
+
+  void clear_region();
+  virtual void begin_draw();
+  virtual void end_draw();
+  virtual void idle();
+
+  virtual void additional_graph_window_paint(cairo_t *cr);
+  virtual DragMode consider_drag_start(int graph_x, int graph_y);
+
+  virtual gboolean handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
+               bool double_click);
+  virtual gboolean handle_button_release(GtkWidget *widget, int graph_x, int graph_y);
+  virtual gboolean handle_motion(GtkWidget *widget, int graph_x, int graph_y);
+
+private:
+  void draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar);
+  void draw_guide_labels(cairo_t *cr);
+  void draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar);
+
+  static void toggled_callback(GtkToggleButton *button, gpointer data);
+  static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data);
+
+private:
+  std::string _net_value_text;
+  pmap<int, GtkStatsLabel *> _labels;
+
+  GtkWidget *_top_hbox;
+  GtkWidget *_average_check_box;
+  GtkWidget *_total_label;
+  GtkWidget *_fixed;
+};
+
+#endif

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