فهرست منبع

Merge branch 'master' into shaderpipeline

rdb 4 سال پیش
والد
کامیت
66dcfc810d
100فایلهای تغییر یافته به همراه1579 افزوده شده و 363 حذف شده
  1. 5 5
      .github/workflows/ci.yml
  2. 1 0
      BACKERS.md
  3. 1 1
      CONTRIBUTING.md
  4. 4 11
      README.md
  5. 10 2
      contrib/src/rplight/pssmCameraRig.cxx
  6. 4 0
      direct/src/dcparser/dcPacker_ext.cxx
  7. 6 5
      direct/src/dist/FreezeTool.py
  8. 4 1
      direct/src/dist/commands.py
  9. 2 0
      direct/src/filter/FilterManager.py
  10. 8 0
      direct/src/interval/MetaInterval.py
  11. 0 2
      direct/src/showbase/PythonUtil.py
  12. 1 1
      direct/src/showbase/ShowBase.py
  13. 33 0
      doc/ReleaseNotes
  14. 1 1
      doc/man/bam-info.1
  15. 107 42
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  16. 1 1
      dtool/src/interrogatedb/py_wrappers.cxx
  17. 12 0
      makepanda/installer.nsi
  18. 2 1
      makepanda/makepanda.py
  19. 6 2
      makepanda/test_wheel.py
  20. 6 5
      panda/src/bullet/bulletDebugNode.I
  21. 4 4
      panda/src/bullet/bulletHeightfieldShape.cxx
  22. 10 5
      panda/src/bullet/bulletPersistentManifold.cxx
  23. 1 1
      panda/src/bullet/bulletPersistentManifold.h
  24. 3 3
      panda/src/bullet/bulletWorld.cxx
  25. 1 1
      panda/src/bullet/bulletWorld.h
  26. 0 3
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  27. 9 20
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  28. 1 1
      panda/src/collide/collisionTraverser_ext.cxx
  29. 5 0
      panda/src/display/config_display.cxx
  30. 1 0
      panda/src/display/config_display.h
  31. 32 0
      panda/src/display/displayRegion.I
  32. 13 2
      panda/src/display/displayRegion.cxx
  33. 7 0
      panda/src/display/displayRegion.h
  34. 3 0
      panda/src/display/graphicsOutput.cxx
  35. 3 3
      panda/src/display/graphicsPipeSelection.cxx
  36. 0 17
      panda/src/display/graphicsWindow.cxx
  37. 0 3
      panda/src/display/graphicsWindow.h
  38. 3 0
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  39. 17 10
      panda/src/event/asyncFuture.cxx
  40. 6 3
      panda/src/event/asyncFuture_ext.cxx
  41. 6 4
      panda/src/express/trueClock.cxx
  42. 1 1
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  43. 1 1
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  44. 2 0
      panda/src/gles2gsg/gles2gsg.h
  45. 24 0
      panda/src/gles2gsg/panda_esgl2ext.h
  46. 10 6
      panda/src/glstuff/glGraphicsBuffer_src.I
  47. 5 0
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  48. 8 6
      panda/src/glstuff/glGraphicsBuffer_src.h
  49. 295 161
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  50. 16 8
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  51. 16 0
      panda/src/glstuff/glShaderContext_src.cxx
  52. 13 0
      panda/src/gobj/geom.cxx
  53. 6 7
      panda/src/gobj/texture.I
  54. 101 11
      panda/src/gobj/texture.cxx
  55. 12 0
      panda/src/gobj/texture.h
  56. 18 0
      panda/src/gobj/textureContext.I
  57. 3 0
      panda/src/gobj/textureContext.h
  58. 0 1
      panda/src/gobj/texturePeeker.h
  59. 8 0
      panda/src/linmath/lmatrix3_ext_src.I
  60. 3 0
      panda/src/linmath/lmatrix3_ext_src.h
  61. 2 0
      panda/src/linmath/lmatrix3_src.h
  62. 8 0
      panda/src/linmath/lmatrix4_ext_src.I
  63. 3 0
      panda/src/linmath/lmatrix4_ext_src.h
  64. 2 0
      panda/src/linmath/lmatrix4_src.h
  65. 8 0
      panda/src/linmath/lpoint2_ext_src.I
  66. 3 0
      panda/src/linmath/lpoint2_ext_src.h
  67. 2 0
      panda/src/linmath/lpoint2_src.h
  68. 8 0
      panda/src/linmath/lpoint3_ext_src.I
  69. 3 0
      panda/src/linmath/lpoint3_ext_src.h
  70. 2 0
      panda/src/linmath/lpoint3_src.h
  71. 8 0
      panda/src/linmath/lpoint4_ext_src.I
  72. 3 0
      panda/src/linmath/lpoint4_ext_src.h
  73. 2 0
      panda/src/linmath/lpoint4_src.h
  74. 59 0
      panda/src/linmath/lvecBase2_ext_src.I
  75. 4 0
      panda/src/linmath/lvecBase2_ext_src.h
  76. 4 0
      panda/src/linmath/lvecBase2_src.h
  77. 61 0
      panda/src/linmath/lvecBase3_ext_src.I
  78. 4 0
      panda/src/linmath/lvecBase3_ext_src.h
  79. 4 0
      panda/src/linmath/lvecBase3_src.h
  80. 62 0
      panda/src/linmath/lvecBase4_ext_src.I
  81. 4 0
      panda/src/linmath/lvecBase4_ext_src.h
  82. 4 0
      panda/src/linmath/lvecBase4_src.h
  83. 8 0
      panda/src/linmath/lvector2_ext_src.I
  84. 3 0
      panda/src/linmath/lvector2_ext_src.h
  85. 2 0
      panda/src/linmath/lvector2_src.h
  86. 8 0
      panda/src/linmath/lvector3_ext_src.I
  87. 3 0
      panda/src/linmath/lvector3_ext_src.h
  88. 2 0
      panda/src/linmath/lvector3_src.h
  89. 8 0
      panda/src/linmath/lvector4_ext_src.I
  90. 3 0
      panda/src/linmath/lvector4_ext_src.h
  91. 2 0
      panda/src/linmath/lvector4_src.h
  92. 2 0
      panda/src/pgraph/CMakeLists.txt
  93. 3 0
      panda/src/pgraph/config_pgraph.cxx
  94. 49 0
      panda/src/pgraph/depthBiasAttrib.I
  95. 180 0
      panda/src/pgraph/depthBiasAttrib.cxx
  96. 115 0
      panda/src/pgraph/depthBiasAttrib.h
  97. 2 0
      panda/src/pgraph/depthOffsetAttrib.h
  98. 42 1
      panda/src/pgraph/nodePath.cxx
  99. 5 0
      panda/src/pgraph/nodePath.h
  100. 1 0
      panda/src/pgraph/p3pgraph_composite2.cxx

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

@@ -11,7 +11,7 @@ jobs:
 
       matrix:
         profile:
-        - ubuntu-xenial-standard-unity-makefile
+        - ubuntu-bionic-standard-unity-makefile
         - ubuntu-bionic-coverage-ninja
         - macos-eigen-coverage-unity-xcode
         - macos-nometa-standard-makefile
@@ -19,8 +19,8 @@ jobs:
         - windows-nopython-nometa-standard-msvc
 
         include:
-        - profile: ubuntu-xenial-standard-unity-makefile
-          os: ubuntu-16.04
+        - profile: ubuntu-bionic-standard-unity-makefile
+          os: ubuntu-18.04
           config: Standard
           unity: YES
           generator: Unix Makefiles
@@ -333,12 +333,12 @@ jobs:
     if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
     strategy:
       matrix:
-        os: [ubuntu-16.04, windows-2016, macOS-latest]
+        os: [ubuntu-18.04, windows-2016, macOS-latest]
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v1
     - name: Install dependencies (Ubuntu)
-      if: matrix.os == 'ubuntu-16.04'
+      if: matrix.os == 'ubuntu-18.04'
       run: |
         sudo apt-get update
         sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev

+ 1 - 0
BACKERS.md

@@ -34,6 +34,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Kyle Roach
 * Brian Lach
 * C0MPU73R
+* Maxwell Dreytser
 
 ## Backers
 

+ 1 - 1
CONTRIBUTING.md

@@ -18,7 +18,7 @@ It is important for acceptance that the change is implemented in a way that fits
 the general design principles of the Panda3D API, and fits well with the general
 priorities of the team.  Therefore, prior discussion with other developers is
 critical.  Issues can be used to facilitate this, but we also invite you to
-visit the #development channel on Discord (or #panda3d-devel on FreeNode IRC).
+visit the #development channel on Discord (or #panda3d-devel on Libera Chat).
 
 We also recommend that you familiarize yourself with the established coding
 style and design patterns of Panda3D, to reduce the amount of changes that have

+ 4 - 11
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-9/).
+[this page](https://www.panda3d.org/download/sdk-1-10-10/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 
-- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win32.zip
+- 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
 
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
@@ -136,7 +136,7 @@ macOS
 -----
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz).
+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).
 
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
@@ -249,10 +249,3 @@ to everyone who has donated!
 <a href="https://opencollective.com/panda3d" target="_blank">
   <img src="https://opencollective.com/panda3d/contribute/[email protected]?color=blue" width=300 />
 </a>
-
-### Gold Sponsors
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/0/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/0/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/1/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/1/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/2/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/2/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/3/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/3/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/4/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/4/website)

+ 10 - 2
contrib/src/rplight/pssmCameraRig.cxx

@@ -369,8 +369,16 @@ void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) {
   LMatrix4 transform = cam_node.get_transform()->get_mat();
 
   // Get Camera and Lens pointers
-  Camera* cam = DCAST(Camera, cam_node.get_child(0).node());
-  nassertv(cam != nullptr);
+  Camera *cam;
+  PandaNode *node = cam_node.node();
+  if (node->is_of_type(Camera::get_class_type())) {
+    cam = (Camera *)node;
+  }
+  else {
+    // Perhaps we passed in something like base.camera ?
+    cam = DCAST(Camera, cam_node.get_child(0).node());
+    nassertv(cam != nullptr);
+  }
   Lens* lens = cam->get_lens();
 
   // Extract near and far points:

+ 4 - 0
direct/src/dcparser/dcPacker_ext.cxx

@@ -226,6 +226,10 @@ unpack_object() {
       std::string str;
       _this->unpack_string(str);
       object = PyUnicode_FromStringAndSize(str.data(), str.size());
+      if (object == nullptr) {
+        nassert_raise("Unable to decode UTF-8 string; use blob type for binary data");
+        return nullptr;
+      }
     }
     break;
 

+ 6 - 5
direct/src/dist/FreezeTool.py

@@ -90,6 +90,7 @@ ignoreImports = {
     'direct.showbase.PythonUtil': ['pstats', 'profile'],
 
     'toml.encoder': ['numpy'],
+    'py._builtin': ['__builtin__'],
 }
 
 if sys.version_info >= (3, 8):
@@ -106,7 +107,7 @@ overrideModules = {
     # Used by the warnings module, among others, to get line numbers.  Since
     # we set __file__, this would cause it to try and extract Python code
     # lines from the main executable, which we don't want.
-    'linecache': """__all__ = ["getline", "clearcache", "checkcache"]
+    'linecache': """__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
 
 cache = {}
 
@@ -662,7 +663,7 @@ okMissing = [
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
     'direct.extensions_native.extensions_darwin', '_manylinux',
     'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
-    'collections.Sequence', 'numpy_distutils',
+    'collections.Sequence', 'numpy_distutils', '_winapi',
     ]
 
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -2625,9 +2626,9 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where
         stuff is a tuple like (suffix, mode, type). """
 
-        if imp.is_frozen(name):
-            # Don't pick up modules that are frozen into p3dpython.
-            raise ImportError("'%s' is a frozen module" % (name))
+        #if imp.is_frozen(name):
+        #    # Don't pick up modules that are frozen into p3dpython.
+        #    raise ImportError("'%s' is a frozen module" % (name))
 
         if parent is not None:
             fullname = parent.__name__+'.'+name

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

@@ -95,6 +95,7 @@ PACKAGE_DATA_DIRS = {
         ('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
     ],
     'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
+    'certifi': [('certifi/cacert.pem', '', {})],
 }
 
 # Some dependencies have extra directories that need to be scanned for DLLs.
@@ -180,7 +181,7 @@ class build_apps(setuptools.Command):
         self.exclude_modules = {}
         self.icons = {}
         self.platforms = [
-            'manylinux1_x86_64',
+            'manylinux2010_x86_64',
             'macosx_10_9_x86_64',
             'win_amd64',
         ]
@@ -1330,6 +1331,8 @@ class bdist_apps(setuptools.Command):
     DEFAULT_INSTALLERS = {
         'manylinux1_x86_64': ['gztar'],
         'manylinux1_i686': ['gztar'],
+        'manylinux2010_x86_64': ['gztar'],
+        'manylinux2010_i686': ['gztar'],
         # Everything else defaults to ['zip']
     }
 

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

@@ -367,6 +367,8 @@ class FilterManager(DirectObject):
         self.camstate = self.caminit
         self.camera.node().setInitialState(self.caminit)
         self.region.setCamera(self.camera)
+        if hasattr(self.region, 'clearCullResult'):
+            self.region.clearCullResult()
         self.nextsort = self.win.getSort() - 9
         self.basex = 0
         self.basey = 0

+ 8 - 0
direct/src/interval/MetaInterval.py

@@ -348,10 +348,14 @@ class MetaInterval(CMetaInterval):
     def getManager(self):
         return self.__manager
 
+    manager = property(getManager, setManager)
+
     def setT(self, t):
         self.__updateIvals()
         CMetaInterval.setT(self, t)
 
+    t = property(CMetaInterval.getT, setT)
+
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
         self.__updateIvals()
         self.setupPlay(startT, endT, playRate, 0)
@@ -475,6 +479,8 @@ class MetaInterval(CMetaInterval):
         else:
             CMetaInterval.setPlayRate(self, playRate)
 
+    play_rate = property(CMetaInterval.getPlayRate, setPlayRate)
+
     def __doPythonCallbacks(self):
         # This function invokes any Python-level Intervals that need
         # to be invoked at this point in time.  It must be called
@@ -549,6 +555,8 @@ class MetaInterval(CMetaInterval):
         self.__updateIvals()
         return CMetaInterval.getDuration(self)
 
+    duration = property(getDuration)
+
     def __repr__(self, *args, **kw):
         # This function overrides from the parent level to force it to
         # update the interval list first, if necessary.

+ 0 - 2
direct/src/showbase/PythonUtil.py

@@ -1127,8 +1127,6 @@ def normalDistrib(a, b, gauss=random.gauss):
     uniformly onto the curve inside [a, b]
 
     ------------------------------------------------------------------------
-    https://statweb.stanford.edu/~naras/jsm/NormalDensity/NormalDensity.html
-
     The 68-95-99.7% Rule
     ====================
     All normal density curves satisfy the following property which is often

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

@@ -725,7 +725,7 @@ class ShowBase(DirectObject.DirectObject):
                               of :meth:`~panda3d.core.GraphicsWindow.setUnexposedDraw()`.
 
         :param callbackWindowDict: If not None, a
-                                   :class:`~panda3d.core.CallbackGraphicWindow`
+                                   :class:`~panda3d.core.CallbackGraphicsWindow`
                                    is created instead, which allows the caller
                                    to create the actual window with its own
                                    OpenGL context, and direct Panda's rendering

+ 33 - 0
doc/ReleaseNotes

@@ -1,3 +1,36 @@
+-----------------------  RELEASE 1.10.10  -----------------------
+
+This release fixes assorted, mostly very minor bugs.
+
+* Round refresh rates when choosing display mode on macOS (#1144)
+* Vectors now support floor division
+* It is now possible to use round(), ceil() and floor() with vector types
+* Add RenderState::get_unused_states()
+* Fix assertion error in RenderState::get_num_unused_states()
+* Fix Assimp loader not importing normal vectors correctly (#1163)
+* Fix error when trying to render DirectGUI in offscreen mode (#1174)
+* Fix crash when resizing window in multi-window DirectX 9 application (#1167)
+* Fixes to enable compilation with recent OpenEXR and FFMpeg versions
+* Fix draw callback being called twice if cull callback calls upcall()
+* Improve error message when display module fails to load
+* Fix writing/reading BitArray to/from bam files on 64-bit systems (#1181)
+* evdev input devices (such as gamepads) are now supported in FreeBSD as well
+* Panda no longer tries to compress buffer textures when compression is enabled
+* Fix Geom::make_lines_in_place() (& points, patches) leaving invalid state
+* Fix memory leak when cleaning up FilterManager (#1166)
+* Fix memory leak deleting multisample OpenGL FBOs (#1166)
+* Fix auto-binding of SSBOs sometimes causing overlapping bindings (#1176)
+* PSSMCameraRig::update() now accepts a camera node directly
+* Support copying depth buffer for 32-bit depth with 8-bit stencil (#1142)
+* Prevent trying to copy depth from non-depth buffer in OpenGL renderer (#1142)
+* Fixes to format selection for OpenGL renderbuffers (#1137, #1141)
+* gl-depth-zero-to-one is now supported in OpenGL ES 2+ (if driver supports)
+* Maya models can contain more than three eggObjectTypes (#1134)
+* Fix black screen on Linux when switching fullscreen without a WM active
+* Fix Linux crash when trying to load a directory instead of a file (#1140)
+* Fix crash when loading an invalid font
+* Fix a very obscure unintended DirectGUI behavior change in 1.10.9
+
 ------------------------  RELEASE 1.10.9  -----------------------
 
 This is a bugfix release which addresses some severe issues on macOS, as well as

+ 1 - 1
doc/man/bam-info.1

@@ -15,7 +15,7 @@ List the scene graph hierarchy in the bam file.
 List explicitly each transition in the hierarchy.
 .TP
 .B \-g
-Output verbose information about the each Geom in the Bam file.
+Output verbose information about each Geom in the Bam file.
 .TP
 .B \-h
 Display this help page.

+ 107 - 42
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -307,7 +307,9 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
   string method_name = func->_ifunc.get_name();
   bool is_unary_op = func->_ifunc.is_unary_op();
 
-  if (method_name == "operator +") {
+  if (method_name == "operator +" ||
+      method_name == "__add__" ||
+      method_name == "__radd__") {
     def._answer_location = "nb_add";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -319,13 +321,17 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "operator -") {
+  if (method_name == "operator -" ||
+      method_name == "__sub__" ||
+      method_name == "__rsub__") {
     def._answer_location = "nb_subtract";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator *") {
+  if (method_name == "operator *" ||
+      method_name == "__mul__" ||
+      method_name == "__rmul__") {
     def._answer_location = "nb_multiply";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -337,37 +343,47 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "__truediv__") {
+  if (method_name == "__truediv__" ||
+      method_name == "__rtruediv__") {
     def._answer_location = "nb_true_divide";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "__floordiv__") {
+  if (method_name == "__floordiv__" ||
+      method_name == "__rfloordiv__") {
     def._answer_location = "nb_floor_divide";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator %") {
+  if (method_name == "operator %" ||
+      method_name == "__mod__" ||
+      method_name == "__rmod__") {
     def._answer_location = "nb_remainder";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator <<") {
+  if (method_name == "operator <<" ||
+      method_name == "__lshift__" ||
+      method_name == "__rlshift__") {
     def._answer_location = "nb_lshift";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator >>") {
+  if (method_name == "operator >>" ||
+      method_name == "__rshift__" ||
+      method_name == "__rrshift__") {
     def._answer_location = "nb_rshift";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator ^") {
+  if (method_name == "operator ^" ||
+      method_name == "__xor__" ||
+      method_name == "__rxor__") {
     def._answer_location = "nb_xor";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -379,13 +395,17 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "operator &") {
+  if (method_name == "operator &" ||
+      method_name == "__and__" ||
+      method_name == "__rand__") {
     def._answer_location = "nb_and";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator |") {
+  if (method_name == "operator |" ||
+      method_name == "__or__" ||
+      method_name == "__ror__") {
     def._answer_location = "nb_or";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -1896,16 +1916,8 @@ write_module_class(ostream &out, Object *obj) {
         break;
 
       case WT_one_param:
-      case WT_binary_operator:
-      case WT_inplace_binary_operator:
         // PyObject *func(PyObject *self, PyObject *one)
         {
-          int return_flags = RF_err_null;
-          if (rfi->second._wrapper_type == WT_inplace_binary_operator) {
-            return_flags |= RF_self;
-          } else {
-            return_flags |= RF_pyobject;
-          }
           bool all_nonconst = true;
           for (FunctionRemap *remap : def._remaps) {
             if (remap->_const_method) {
@@ -1918,20 +1930,7 @@ write_module_class(ostream &out, Object *obj) {
           out << "//////////////////\n";
           out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
           out << "  " << cClassName << " *local_this = nullptr;\n";
-          if (rfi->second._wrapper_type != WT_one_param) {
-            // WT_binary_operator means we must return NotImplemented, instead
-            // of raising an exception, if the this pointer doesn't match.
-            // This is for things like __sub__, which Python likes to call on
-            // the wrong-type objects.
-            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
-            if (all_nonconst) {
-              out << "  if (local_this == nullptr || DtoolInstance_IS_CONST(self)) {\n";
-            } else {
-              out << "  if (local_this == nullptr) {\n";
-            }
-            out << "    Py_INCREF(Py_NotImplemented);\n";
-            out << "    return Py_NotImplemented;\n";
-          } else if (all_nonconst) {
+          if (all_nonconst) {
             out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_"
                 << ClassName << ", (void **)&local_this, \"" << ClassName
                 << "." << methodNameFromCppName(fname, "", false) << "\")) {\n";
@@ -1944,19 +1943,85 @@ write_module_class(ostream &out, Object *obj) {
 
           string expected_params;
           write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
-                                AT_single_arg, return_flags, false, !all_nonconst);
+                                AT_single_arg, RF_err_null | RF_pyobject, false, !all_nonconst);
+
+          out << "  if (!_PyErr_OCCURRED()) {\n";
+          out << "    return Dtool_Raise_BadArgumentsError(\n";
+          output_quoted(out, 6, expected_params);
+          out << ");\n";
+          out << "  }\n";
+          out << "  return nullptr;\n";
+          out << "}\n\n";
+        }
+        break;
 
-          if (rfi->second._wrapper_type != WT_one_param) {
-            out << "  Py_INCREF(Py_NotImplemented);\n";
-            out << "  return Py_NotImplemented;\n";
+      case WT_binary_operator:
+      case WT_inplace_binary_operator:
+        // PyObject *func(PyObject *self, PyObject *one)
+        {
+          int return_flags = RF_err_null;
+          if (rfi->second._wrapper_type == WT_inplace_binary_operator) {
+            return_flags |= RF_self;
           } else {
-            out << "  if (!_PyErr_OCCURRED()) {\n";
-            out << "    return Dtool_Raise_BadArgumentsError(\n";
-            output_quoted(out, 6, expected_params);
-            out << ");\n";
+            return_flags |= RF_pyobject;
+          }
+          bool forward_all_nonconst = true;
+          bool reverse_all_nonconst = true;
+          set<FunctionRemap *> forward_remaps;
+          set<FunctionRemap *> reverse_remaps;
+          for (FunctionRemap *remap : def._remaps) {
+            std::string fname = remap->_cppfunc->get_simple_name();
+            if (fname.compare(0, 3, "__r") == 0 && fname != "__rshift__") {
+              reverse_remaps.insert(remap);
+              if (remap->_const_method) {
+                reverse_all_nonconst = false;
+              }
+            } else {
+              forward_remaps.insert(remap);
+              if (remap->_const_method) {
+                forward_all_nonconst = false;
+              }
+            }
+          }
+          out << "//////////////////\n";
+          out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
+          out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
+          out << "//////////////////\n";
+          out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
+          out << "  " << cClassName << " *local_this = nullptr;\n";
+          // WT_binary_operator means we must return NotImplemented, instead
+          // of raising an exception, if the this pointer doesn't match.
+          // This is for things like __sub__, which Python likes to call on
+          // the wrong-type objects.
+          if (!forward_remaps.empty()) {
+            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
+            if (forward_all_nonconst) {
+              out << "  if (local_this != nullptr && !DtoolInstance_IS_CONST(self)) {\n";
+            } else {
+              out << "  if (local_this != nullptr) {\n";
+            }
+            string expected_params;
+            write_function_forset(out, forward_remaps, 1, 1, expected_params, 4, true, true,
+                                  AT_single_arg, return_flags, false, !forward_all_nonconst);
             out << "  }\n";
-            out << "  return nullptr;\n";
           }
+
+          if (!reverse_remaps.empty()) {
+            out << "  std::swap(self, arg);\n";
+            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
+            if (reverse_all_nonconst) {
+              out << "  if (local_this != nullptr && !DtoolInstance_IS_CONST(self)) {\n";
+            } else {
+              out << "  if (local_this != nullptr) {\n";
+            }
+            string expected_params;
+            write_function_forset(out, reverse_remaps, 1, 1, expected_params, 4, true, true,
+                                  AT_single_arg, return_flags, false, !reverse_all_nonconst);
+            out << "  }\n";
+          }
+
+          out << "  Py_INCREF(Py_NotImplemented);\n";
+          out << "  return Py_NotImplemented;\n";
           out << "}\n\n";
         }
         break;

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

@@ -371,7 +371,7 @@ static PyObject *Dtool_MutableSequenceWrapper_insert(PyObject *self, PyObject *a
       return PyErr_Format(PyExc_TypeError, "%s.insert() does not support negative indices", wrap->_base._name);
     }
   }
-  return wrap->_insert_func(wrap->_base._self, (ssize_t)std::max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
+  return wrap->_insert_func(wrap->_base._self, (size_t)std::max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
 }
 
 /**

+ 12 - 0
makepanda/installer.nsi

@@ -374,6 +374,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
         !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
+        !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
@@ -381,6 +382,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
         !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
+        !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
     !endif
 SectionGroupEnd
 
@@ -489,6 +491,7 @@ Function .onInit
         ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9-32
         !insertmacro MaybeEnablePyBindingSection 3.10-32
+        !insertmacro MaybeEnablePyBindingSection 3.11-32
         ${EndIf}
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
@@ -498,6 +501,7 @@ Function .onInit
         ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9
         !insertmacro MaybeEnablePyBindingSection 3.10
+        !insertmacro MaybeEnablePyBindingSection 3.11
         ${EndIf}
     !endif
 
@@ -511,6 +515,10 @@ Function .onInit
         SectionSetFlags ${SecPyBindings3.10} ${SF_RO}
         SectionSetInstTypes ${SecPyBindings3.10} 0
     !endif
+    !ifdef SecPyBindings3.11
+        SectionSetFlags ${SecPyBindings3.11} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.11} 0
+    !endif
     ${EndUnless}
 FunctionEnd
 
@@ -822,6 +830,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.8-32
         !insertmacro RemovePythonPath 3.9-32
         !insertmacro RemovePythonPath 3.10-32
+        !insertmacro RemovePythonPath 3.11-32
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
@@ -829,6 +838,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.8
         !insertmacro RemovePythonPath 3.9
         !insertmacro RemovePythonPath 3.10
+        !insertmacro RemovePythonPath 3.11
     !endif
 
     SetDetailsPrint both
@@ -897,6 +907,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
@@ -904,6 +915,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
   !endif
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 2 - 1
makepanda/makepanda.py

@@ -659,6 +659,7 @@ if (COMPILER == "MSVC"):
         else:
             LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib")
         IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/OpenEXR")
+        IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/Imath")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
     if (PkgSkip("ZLIB")==0):     LibName("ZLIB",     GetThirdpartyDir() + "zlib/lib/zlibstatic.lib")
     if (PkgSkip("VRPN")==0):     LibName("VRPN",     GetThirdpartyDir() + "vrpn/lib/vrpn.lib")
@@ -850,7 +851,7 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("OPENAL",    "openal",    ("openal"), "AL/al.h", framework = "OpenAL")
     SmartPkgEnable("SQUISH",    "",          ("squish"), "squish.h")
     SmartPkgEnable("TIFF",      "libtiff-4", ("tiff"), "tiff.h")
-    SmartPkgEnable("OPENEXR",   "OpenEXR",   ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), ("OpenEXR", "OpenEXR/ImfOutputFile.h"))
+    SmartPkgEnable("OPENEXR",   "OpenEXR",   ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), ("OpenEXR", "Imath", "OpenEXR/ImfOutputFile.h"))
     SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
     SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.h"))
     SmartPkgEnable("VORBIS",    "vorbisfile",("vorbisfile", "vorbis", "ogg"), ("ogg/ogg.h", "vorbis/vorbisfile.h"))

+ 6 - 2
makepanda/test_wheel.py

@@ -38,8 +38,12 @@ def test_wheel(wheel, verbose=False):
 
     # Install pytest into the environment, as well as our wheel.
     packages = ["pytest", wheel]
-    if sys.version_info[0:2] == (3, 4) and sys.platform == "win32":
-        packages += ["colorama==0.4.1"]
+    if sys.version_info[0:2] == (3, 4):
+        if sys.platform == "win32":
+            packages += ["colorama==0.4.1"]
+
+        # See https://github.com/python-attrs/attrs/pull/807
+        packages += ["attrs<21"]
 
     if subprocess.call([python, "-m", "pip", "install"] + packages) != 0:
         shutil.rmtree(envdir)

+ 6 - 5
panda/src/bullet/bulletDebugNode.I

@@ -20,7 +20,7 @@ INLINE BulletDebugNode::
 }
 
 /**
- *
+ * If true, displays collision shapes in wireframe mode.
  */
 INLINE void BulletDebugNode::
 show_wireframe(bool show) {
@@ -39,7 +39,8 @@ get_show_wireframe() const {
 }
 
 /**
- *
+ * If true, display limits defined for constraints, e.g. a pivot axis or maximum
+ * amplitude.
  */
 INLINE void BulletDebugNode::
 show_constraints(bool show) {
@@ -58,7 +59,7 @@ get_show_constraints() const {
 }
 
 /**
- *
+ * If true, displays axis aligned bounding boxes for objects.
  */
 INLINE void BulletDebugNode::
 show_bounding_boxes(bool show) {
@@ -77,7 +78,7 @@ get_show_bounding_boxes() const {
 }
 
 /**
- *
+ * If true, displays normal vectors for triangle mesh and heightfield faces.
  */
 INLINE void BulletDebugNode::
 show_normals(bool show) {
@@ -92,4 +93,4 @@ INLINE bool BulletDebugNode::
 get_show_normals() const {
 
   return _drawer._normals;
-}
+}

+ 4 - 4
panda/src/bullet/bulletHeightfieldShape.cxx

@@ -44,7 +44,7 @@ BulletHeightfieldShape(const PNMImage &image, PN_stdfloat max_height, BulletUpAx
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          max_height,
                                          up,
                                          true, false);
@@ -102,7 +102,7 @@ BulletHeightfieldShape(Texture *tex, PN_stdfloat max_height, BulletUpAxis up) :
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          max_height,
                                          up,
                                          true, false);
@@ -127,7 +127,7 @@ BulletHeightfieldShape(const BulletHeightfieldShape &copy) {
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          _max_height,
                                          _up,
                                          true, false);
@@ -208,7 +208,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          _max_height,
                                          _up,
                                          true, false);

+ 10 - 5
panda/src/bullet/bulletPersistentManifold.cxx

@@ -49,6 +49,8 @@ get_contact_processing_threshold() const {
  */
 void BulletPersistentManifold::
 clear_manifold() {
+  nassertv_always(_manifold != nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
   _manifold->clearManifold();
@@ -59,6 +61,8 @@ clear_manifold() {
  */
 PandaNode *BulletPersistentManifold::
 get_node0() {
+  nassertr_always(_manifold != nullptr, nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
 #if BT_BULLET_VERSION >= 281
@@ -75,6 +79,8 @@ get_node0() {
  */
 PandaNode *BulletPersistentManifold::
 get_node1() {
+  nassertr_always(_manifold != nullptr, nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
 #if BT_BULLET_VERSION >= 281
@@ -91,6 +97,8 @@ get_node1() {
  */
 int BulletPersistentManifold::
 get_num_manifold_points() const {
+  nassertr_always(_manifold != nullptr, 0);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
   return _manifold->getNumContacts();
@@ -99,11 +107,8 @@ get_num_manifold_points() const {
 /**
  *
  */
-BulletManifoldPoint *BulletPersistentManifold::
+BulletManifoldPoint BulletPersistentManifold::
 get_manifold_point(int idx) const {
   LightMutexHolder holder(BulletWorld::get_global_lock());
-
-  nassertr(idx < _manifold->getNumContacts(), nullptr)
-
-  return new BulletManifoldPoint(_manifold->getContactPoint(idx));
+  return BulletManifoldPoint(_manifold->getContactPoint(idx));
 }

+ 1 - 1
panda/src/bullet/bulletPersistentManifold.h

@@ -34,7 +34,7 @@ PUBLISHED:
   PandaNode *get_node1();
 
   int get_num_manifold_points() const;
-  BulletManifoldPoint *get_manifold_point(int idx) const;
+  BulletManifoldPoint get_manifold_point(int idx) const;
   MAKE_SEQ(get_manifold_points, get_num_manifold_points, get_manifold_point);
 
   PN_stdfloat get_contact_breaking_threshold() const;

+ 3 - 3
panda/src/bullet/bulletWorld.cxx

@@ -1064,14 +1064,14 @@ contact_test_pair(PandaNode *node0, PandaNode *node1) const {
 /**
  *
  */
-BulletPersistentManifold *BulletWorld::
+BulletPersistentManifold BulletWorld::
 get_manifold(int idx) const {
   LightMutexHolder holder(get_global_lock());
 
-  nassertr(idx < _dispatcher->getNumManifolds(), nullptr);
+  nassertr(idx < _dispatcher->getNumManifolds(), BulletPersistentManifold(nullptr));
 
   btPersistentManifold *ptr = _dispatcher->getManifoldByIndexInternal(idx);
-  return (ptr) ? new BulletPersistentManifold(ptr) : nullptr;
+  return BulletPersistentManifold(ptr);
 }
 
 /**

+ 1 - 1
panda/src/bullet/bulletWorld.h

@@ -127,7 +127,7 @@ PUBLISHED:
 
   // Manifolds
   int get_num_manifolds() const;
-  BulletPersistentManifold *get_manifold(int idx) const;
+  BulletPersistentManifold get_manifold(int idx) const;
   MAKE_SEQ(get_manifolds, get_num_manifolds, get_manifold);
 
   // Collision filtering

+ 0 - 3
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -71,9 +71,6 @@ protected:
   CFMutableArrayRef find_display_modes(int width, int height);
   bool do_switch_fullscreen(CGDisplayModeRef mode);
 
-  virtual void mouse_mode_absolute();
-  virtual void mouse_mode_relative();
-
 private:
   NSData *load_image_data(const Filename &filename);
   NSImage *load_image(const Filename &filename);

+ 9 - 20
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -693,7 +693,7 @@ open_window() {
   // Enable relative mouse mode, if this was requested.
   if (_properties.has_mouse_mode() &&
       _properties.get_mouse_mode() == WindowProperties::M_relative) {
-    mouse_mode_relative();
+    CGAssociateMouseAndMouseCursorPosition(NO);
   }
 
   _vsync_enabled = sync_video && cocoagsg->setup_vsync();
@@ -747,22 +747,6 @@ close_window() {
   GraphicsWindow::close_window();
 }
 
-/**
- * Overridden from GraphicsWindow.
- */
-void CocoaGraphicsWindow::
-mouse_mode_absolute() {
-  CGAssociateMouseAndMouseCursorPosition(YES);
-}
-
-/**
- * Overridden from GraphicsWindow.
- */
-void CocoaGraphicsWindow::
-mouse_mode_relative() {
-  CGAssociateMouseAndMouseCursorPosition(NO);
-}
-
 /**
  * Applies the requested set of properties to the window, if possible, for
  * instance to request a change in size or minimization status.
@@ -1170,7 +1154,7 @@ find_display_modes(int width, int height) {
 
   // Get the current refresh rate and pixel encoding.
   CFStringRef current_pixel_encoding;
-  int refresh_rate;
+  double refresh_rate;
   mode = CGDisplayCopyDisplayMode(_display);
 
   // First check if the current mode is adequate.
@@ -1204,7 +1188,7 @@ find_display_modes(int width, int height) {
     // the mode width and height but also actual pixel widh and height.
     if (CGDisplayModeGetWidth(mode) == width &&
         CGDisplayModeGetHeight(mode) == height &&
-        CGDisplayModeGetRefreshRate(mode) == refresh_rate &&
+        (int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
         (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_14 ||
         (CGDisplayModeGetPixelWidth(mode) == expected_pixel_width &&
@@ -1212,7 +1196,12 @@ find_display_modes(int width, int height) {
 #endif
         CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
 
-      CFArrayAppendValue(valid_modes, mode);
+      if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
+        // Exact match for refresh rate, prioritize this.
+        CFArrayInsertValueAtIndex(valid_modes, 0, mode);
+      } else {
+        CFArrayAppendValue(valid_modes, mode);
+      }
     }
     CFRelease(pixel_encoding);
   }

+ 1 - 1
panda/src/collide/collisionTraverser_ext.cxx

@@ -60,7 +60,7 @@ __setstate__(PyObject *state) {
   _this->set_name(std::string(data, len));
 
   _this->set_respect_prev_transform(PyTuple_GET_ITEM(state, 1) != Py_False);
-  size_t num_colliders = (ssize_t)PyLong_AsLong(PyTuple_GET_ITEM(state, 2));
+  size_t num_colliders = (size_t)PyLong_AsLong(PyTuple_GET_ITEM(state, 2));
 
   for (size_t i = 0; i < num_colliders; ++i) {
     NodePath *collider = (NodePath *)DtoolInstance_VOID_PTR(PyTuple_GET_ITEM(state, i * 2 + 3));

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

@@ -383,6 +383,11 @@ ConfigVariableFilename subprocess_window
           "and is not used or needed in other environments.  See "
           "WindowProperties::set_subprocess_window()."));
 
+ConfigVariableBool ime_aware
+("ime-aware", false,
+ PRC_DESC("Set this true to show candidate strings in Panda3D rather than via "
+          "an OS-provided external popup window."));
+
 ConfigVariableString framebuffer_mode
 ("framebuffer-mode", "",
  PRC_DESC("No longer has any effect.  Do not use."));

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

@@ -84,6 +84,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableString window_title;
 extern EXPCL_PANDA_DISPLAY ConfigVariableInt parent_window_handle;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool win_unexposed_draw;
 extern EXPCL_PANDA_DISPLAY ConfigVariableFilename subprocess_window;
+extern EXPCL_PANDA_DISPLAY ConfigVariableBool ime_aware;
 
 extern EXPCL_PANDA_DISPLAY ConfigVariableString framebuffer_mode;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool framebuffer_hardware;

+ 32 - 0
panda/src/display/displayRegion.I

@@ -156,6 +156,28 @@ set_dimensions(const LVecBase4 &dimensions) {
   set_dimensions(0, dimensions);
 }
 
+/**
+ * Changes the range of the depth buffer this DisplayRegion writes to.
+ * The parameters range from 0 to 1.  It is legal for the near value to be
+ * larger than the far value.
+ */
+INLINE void DisplayRegion::
+set_depth_range(PN_stdfloat near_depth, PN_stdfloat far_depth) {
+  CDWriter cdata(_cycler, true);
+  cdata->_depth_range.set(near_depth, far_depth);
+}
+
+/**
+ *
+ */
+INLINE void DisplayRegion::
+get_depth_range(PN_stdfloat &near_depth, PN_stdfloat &far_depth) const {
+  CDReader cdata(_cycler);
+  const LVecBase2 &range = cdata->_depth_range;
+  near_depth = range[0];
+  far_depth = range[1];
+}
+
 /**
  * Returns the GraphicsOutput that this DisplayRegion is ultimately associated
  * with, or NULL if no window is associated.
@@ -693,6 +715,16 @@ get_top(int i) const {
   return _cdata->_regions[i]._dimensions[3];
 }
 
+/**
+ *
+ */
+INLINE void DisplayRegionPipelineReader::
+get_depth_range(PN_stdfloat &near_depth, PN_stdfloat &far_depth) const {
+  const LVecBase2 &range = _cdata->_depth_range;
+  near_depth = range[0];
+  far_depth = range[1];
+}
+
 /**
  * Returns the GraphicsOutput that this DisplayRegion is ultimately associated
  * with, or NULL if no window is associated.

+ 13 - 2
panda/src/display/displayRegion.cxx

@@ -514,6 +514,15 @@ get_screenshot() {
   return tex;
 }
 
+/**
+ *
+ */
+void DisplayRegion::
+clear_cull_result() {
+  CDCullWriter cdata_cull(_cycler_cull, true);
+  cdata_cull->_cull_result = nullptr;
+}
+
 /**
  * Returns a special scene graph constructed to represent the results of the
  * last frame's cull operation.
@@ -729,7 +738,8 @@ CData() :
   _stereo_channel(Lens::SC_mono),
   _tex_view_offset(0),
   _target_tex_page(-1),
-  _scissor_enabled(true)
+  _scissor_enabled(true),
+  _depth_range(0, 1)
 {
   _regions.push_back(Region());
 }
@@ -748,7 +758,8 @@ CData(const DisplayRegion::CData &copy) :
   _stereo_channel(copy._stereo_channel),
   _tex_view_offset(copy._tex_view_offset),
   _target_tex_page(copy._target_tex_page),
-  _scissor_enabled(copy._scissor_enabled)
+  _scissor_enabled(copy._scissor_enabled),
+  _depth_range(copy._depth_range)
 {
 }
 

+ 7 - 0
panda/src/display/displayRegion.h

@@ -82,6 +82,9 @@ PUBLISHED:
   virtual void set_dimensions(int i, const LVecBase4 &dimensions);
   MAKE_PROPERTY(dimensions, get_dimensions, set_dimensions);
 
+  INLINE void set_depth_range(PN_stdfloat near, PN_stdfloat far);
+  INLINE void get_depth_range(PN_stdfloat &near, PN_stdfloat &far) const;
+
   INLINE GraphicsOutput *get_window() const;
   GraphicsPipe *get_pipe() const;
   virtual bool is_stereo() const;
@@ -160,6 +163,7 @@ PUBLISHED:
   bool get_screenshot(PNMImage &image);
   PT(Texture) get_screenshot();
 
+  void clear_cull_result();
   virtual PT(PandaNode) make_cull_result_graph();
 
 public:
@@ -232,6 +236,7 @@ private:
     }
 
     Regions _regions;
+    LVecBase2 _depth_range;  // near, far
 
     int _lens_index; // index into which lens of a camera is associated with this display region.  0 is default
 
@@ -332,6 +337,8 @@ public:
   INLINE PN_stdfloat get_bottom(int i = 0) const;
   INLINE PN_stdfloat get_top(int i = 0) const;
 
+  INLINE void get_depth_range(PN_stdfloat &near, PN_stdfloat &far) const;
+
   INLINE GraphicsOutput *get_window() const;
   GraphicsPipe *get_pipe() const;
 

+ 3 - 0
panda/src/display/graphicsOutput.cxx

@@ -834,6 +834,9 @@ get_active_display_region(int n) const {
  * which will be a texture suitable for applying to geometry within the scene
  * rendered into this window.
  *
+ * If you pass zero as the buffer size, the buffer will have the same size as
+ * the host window, and will automatically be resized when the host window is.
+ *
  * If tex is not NULL, it is the texture that will be set up for rendering
  * into; otherwise, a new Texture object will be created.  In either case, the
  * target texture can be retrieved from the return value with

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

@@ -399,7 +399,7 @@ load_named_module(const string &name) {
   if (handle == nullptr) {
     std::string error = load_dso_error();
     display_cat.warning()
-      << "Unable to load " << dlname.get_basename() << ": " << error << std::endl;
+      << "Unable to load " << dlname.to_os_specific() << ": " << error << std::endl;
     return TypeHandle::none();
   }
 
@@ -417,7 +417,7 @@ load_named_module(const string &name) {
   if (dso_symbol == nullptr) {
     // Couldn't find the module function.
     display_cat.warning()
-      << "Unable to find " << symbol_name << " in " << dlname.get_basename()
+      << "Unable to find " << symbol_name << " in " << dlname.to_os_specific()
       << "\n";
 
   } else {
@@ -447,7 +447,7 @@ load_named_module(const string &name) {
     // though, because it may have assigned itself into the
     // GraphicsPipeSelection table.  So we carry on.
     display_cat.warning()
-      << "No default pipe type available for " << dlname.get_basename()
+      << "No default pipe type available for " << dlname.to_os_specific()
       << "\n";
   }
 

+ 0 - 17
panda/src/display/graphicsWindow.cxx

@@ -696,23 +696,6 @@ add_input_device(InputDevice *device) {
   return index;
 }
 
-/**
- * detaches mouse.  Only mouse delta from now on.
- *
- */
-void GraphicsWindow::
-mouse_mode_relative() {
-}
-
-/**
- * reattaches mouse to location
- *
- */
-void GraphicsWindow::
-mouse_mode_absolute() {
-
-}
-
 /**
  * Returns whether the specified event msg is a touch message.
  *

+ 0 - 3
panda/src/display/graphicsWindow.h

@@ -130,9 +130,6 @@ protected:
   virtual bool do_reshape_request(int x_origin, int y_origin, bool has_origin,
                                   int x_size, int y_size);
 
-  virtual void mouse_mode_absolute();
-  virtual void mouse_mode_relative();
-
   // It is an error to call any of the following methods from any thread other
   // than the window thread.
   void system_changed_properties(const WindowProperties &properties);

+ 3 - 0
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -4776,6 +4776,9 @@ release_swap_chain(DXScreenData *new_context) {
       }
       return false;
     }
+    if (new_context->_swap_chain == _swap_chain) {
+      _swap_chain = nullptr;
+    }
   }
   return true;
 }

+ 17 - 10
panda/src/event/asyncFuture.cxx

@@ -315,6 +315,11 @@ wake_task(AsyncTask *task) {
     task->_state = AsyncTask::S_servicing;
     return;
 
+  case AsyncTask::S_active:
+    // It could have already been activated, such as by a cancel() which then
+    // indirectly caused the awaiting future to be cancelled.  Do nothing.
+    return;
+
   case AsyncTask::S_inactive:
     // Schedule it immediately.
     nassertv(task->_manager == nullptr);
@@ -398,28 +403,30 @@ AsyncGatheringFuture(AsyncFuture::Futures futures) :
 bool AsyncGatheringFuture::
 cancel() {
   if (!done()) {
+    if (task_cat.is_debug()) {
+      task_cat.debug()
+        << "Cancelling AsyncGatheringFuture (" << _futures.size() << " futures)\n";
+    }
+
     // Temporarily increase the pending count so that the notify_done()
     // callbacks won't end up causing it to be set to "finished".
     AtomicAdjust::inc(_num_pending);
 
     bool any_cancelled = false;
-    AsyncFuture::Futures::const_iterator it;
-    for (it = _futures.begin(); it != _futures.end(); ++it) {
-      AsyncFuture *fut = *it;
+    for (AsyncFuture *fut : _futures) {
       if (fut->cancel()) {
         any_cancelled = true;
       }
     }
 
-    // Now change state to "cancelled" and call the notify_done() callbacks.
-    // Don't call notify_done() if another thread has beaten us to it.
-    if (set_future_state(FS_cancelled)) {
-      notify_done(false);
+    // If all the futures were cancelled, change state of this future to
+    // "cancelled" and call the notify_done() callbacks.
+    if (!AtomicAdjust::dec(_num_pending)) {
+      if (set_future_state(FS_cancelled)) {
+        notify_done(false);
+      }
     }
 
-    // Decreasing the pending count is kind of pointless now, so we do it only
-    // in a debug build.
-    nassertr(!AtomicAdjust::dec(_num_pending), any_cancelled);
     return any_cancelled;
   } else {
     return false;

+ 6 - 3
panda/src/event/asyncFuture_ext.cxx

@@ -398,6 +398,10 @@ PyObject *Extension<AsyncFuture>::
 get_cancelled_error_type() {
   static PyObject *exc_type = nullptr;
   if (exc_type == nullptr) {
+    // This method should not affect the current exception, so stash it.
+    PyObject *curexc_type, *curexc_value, *curexc_traceback;
+    PyErr_Fetch(&curexc_type, &curexc_value, &curexc_traceback);
+
     // Get the CancelledError that asyncio uses, too.
 #if PY_VERSION_HEX >= 0x03080000
     PyObject *module = PyImport_ImportModule("asyncio.exceptions");
@@ -408,9 +412,6 @@ get_cancelled_error_type() {
       exc_type = PyObject_GetAttrString(module, "CancelledError");
       Py_DECREF(module);
     }
-    else {
-      PyErr_Clear();
-    }
 
     // If we can't get that, we should pretend and make our own.
     if (exc_type == nullptr) {
@@ -424,6 +425,8 @@ get_cancelled_error_type() {
                                             nullptr, nullptr);
 #endif
     }
+
+    PyErr_Restore(curexc_type, curexc_value, curexc_traceback);
   }
   return exc_type;
 }

+ 6 - 4
panda/src/express/trueClock.cxx

@@ -231,10 +231,12 @@ correct_time(double time) {
     // backward in the high-precision clock, since this does appear to happen
     // in a threaded environment.
 
-    clock_cat.debug()
-      << "Clock error detected; elapsed time " << time_delta
-      << "s on high-resolution counter, and " << tod_delta
-      << "s on time-of-day clock.\n";
+    if (clock_cat.is_debug()) {
+      clock_cat.debug()
+        << "Clock error detected; elapsed time " << time_delta
+        << "s on high-resolution counter, and " << tod_delta
+        << "s on time-of-day clock.\n";
+    }
     ++_error_count;
 
     // If both are negative, we call it 0.  If one is negative, we trust the

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

@@ -101,7 +101,7 @@ FfmpegAudioCursor(FfmpegAudio *src) :
   _audio_rate = codecpar->sample_rate;
   _audio_channels = codecpar->channels;
 
-  AVCodec *pAudioCodec = avcodec_find_decoder(codecpar->codec_id);
+  const AVCodec *pAudioCodec = avcodec_find_decoder(codecpar->codec_id);
   if (pAudioCodec == nullptr) {
     cleanup();
     return;

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

@@ -543,7 +543,7 @@ open_stream() {
   _video_timebase = av_q2d(stream->time_base);
   _min_fseek = (int)(3.0 / _video_timebase);
 
-  AVCodec *pVideoCodec = nullptr;
+  const AVCodec *pVideoCodec = nullptr;
   if (ffmpeg_prefer_libvpx) {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 0, 0)
     if (codecpar->codec_id == AV_CODEC_ID_VP9) {

+ 2 - 0
panda/src/gles2gsg/gles2gsg.h

@@ -120,6 +120,8 @@ typedef char GLchar;
 #define GL_ONE_MINUS_SRC1_COLOR GL_ONE_MINUS_SRC1_COLOR_EXT
 #define GL_SRC1_ALPHA GL_SRC1_ALPHA_EXT
 #define GL_ONE_MINUS_SRC1_ALPHA GL_ONE_MINUS_SRC1_ALPHA_EXT
+#define GL_LOWER_LEFT GL_LOWER_LEFT_EXT
+#define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
 
 #define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
 #define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR

+ 24 - 0
panda/src/gles2gsg/panda_esgl2ext.h

@@ -1050,6 +1050,30 @@ GL_APICALL void GL_APIENTRY glBufferStorageEXT (GLenum target, GLsizeiptr size,
 #endif
 #endif /* GL_EXT_buffer_storage */
 
+#ifndef GL_EXT_clear_texture
+#define GL_EXT_clear_texture 1
+typedef void (GL_APIENTRYP PFNGLCLEARTEXIMAGEEXTPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
+typedef void (GL_APIENTRYP PFNGLCLEARTEXSUBIMAGEEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glClearTexImageEXT (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
+GL_APICALL void GL_APIENTRY glClearTexSubImageEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
+#endif
+#endif /* GL_EXT_clear_texture */
+
+#ifndef GL_EXT_clip_control
+#define GL_EXT_clip_control 1
+#define GL_LOWER_LEFT_EXT                 0x8CA1
+#define GL_UPPER_LEFT_EXT                 0x8CA2
+#define GL_NEGATIVE_ONE_TO_ONE_EXT        0x935E
+#define GL_ZERO_TO_ONE_EXT                0x935F
+#define GL_CLIP_ORIGIN_EXT                0x935C
+#define GL_CLIP_DEPTH_MODE_EXT            0x935D
+typedef void (GL_APIENTRYP PFNGLCLIPCONTROLEXTPROC) (GLenum origin, GLenum depth);
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glClipControlEXT (GLenum origin, GLenum depth);
+#endif
+#endif /* GL_EXT_clip_control */
+
 #ifndef GL_EXT_color_buffer_float
 #define GL_EXT_color_buffer_float 1
 #endif /* GL_EXT_color_buffer_float */

+ 10 - 6
panda/src/glstuff/glGraphicsBuffer_src.I

@@ -11,14 +11,18 @@
  * @date 2006-01-15
  */
 
+/**
+ *
+ */
 INLINE int CLP(GraphicsBuffer)::
-get_multisample_count()
-{
-    return _requested_multisamples;
+get_multisample_count() {
+  return _requested_multisamples;
 }
 
+/**
+ *
+ */
 INLINE int CLP(GraphicsBuffer)::
-get_coverage_sample_count()
-{
-    return _requested_coverage_samples;
+get_coverage_sample_count() {
+  return _requested_coverage_samples;
 }

+ 5 - 0
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -1695,6 +1695,11 @@ close_buffer() {
     _fbo.clear();
   }
 
+  if (_fbo_multisample != 0) {
+    glgsg->_glDeleteFramebuffers(1, &_fbo_multisample);
+    _fbo_multisample = 0;
+  }
+
   report_my_gl_errors();
 
   // Release the Gsg

+ 8 - 6
panda/src/glstuff/glGraphicsBuffer_src.h

@@ -23,11 +23,14 @@
  * ARB_draw_buffers extensions.  This design has significant advantages over
  * the older wglGraphicsBuffer and glxGraphicsBuffer:
  *
- * * Can export depth and stencil.  * Supports auxiliary bitplanes.  *
- * Supports non-power-of-two padding.  * Supports tracking of host window
- * size.  * Supports cumulative render-to-texture.  * Faster than pbuffers.  *
- * Can render onto a texture without clearing it first.  * Supports
- * multisample antialiased rendering.
+ * - Can export depth and stencil.
+ * - Supports auxiliary bitplanes.
+ * - Supports non-power-of-two padding.
+ * - Supports tracking of host window size.
+ * - Supports cumulative render-to-texture.
+ * - Faster than pbuffers.
+ * - Can render onto a texture without clearing it first.
+ * - Supports multisample antialiased rendering.
  *
  * Some of these deserve a little explanation.  Auxiliary bitplanes are
  * additional bitplanes above and beyond the normal depth,stencil,color.  One
@@ -45,7 +48,6 @@
  * EXT_framebuffer_blit to allow for multisample antialiasing these offscreen
  * render targets.  If these extensions are unavailable the buffer will render
  * as if multisamples is 0.
- *
  */
 class EXPCL_GL CLP(GraphicsBuffer) : public GraphicsBuffer {
 public:

+ 295 - 161
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -52,6 +52,7 @@
 #include "alphaTestAttrib.h"
 #include "clipPlaneAttrib.h"
 #include "cullFaceAttrib.h"
+#include "depthBiasAttrib.h"
 #include "depthOffsetAttrib.h"
 #include "depthWriteAttrib.h"
 #include "fogAttrib.h"
@@ -147,6 +148,13 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 }
 #endif
 
+#ifndef OPENGLES_1
+static void APIENTRY
+null_glPolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp) {
+  glPolygonOffset(factor, units);
+}
+#endif
+
 #ifndef OPENGLES_1
 // We have a default shader that will be applied when there isn't any shader
 // applied (e.g.  if it failed to compile).  We need this because OpenGL ES
@@ -467,6 +475,7 @@ CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) :
 
   _scissor_enabled = false;
   _scissor_attrib_active = false;
+  _has_attrib_depth_range = false;
 
   _white_texture = 0;
 
@@ -560,6 +569,7 @@ reset() {
   _inv_state_mask.clear_bit(ColorAttrib::get_class_slot());
   _inv_state_mask.clear_bit(ColorScaleAttrib::get_class_slot());
   _inv_state_mask.clear_bit(CullFaceAttrib::get_class_slot());
+  _inv_state_mask.clear_bit(DepthBiasAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthOffsetAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthTestAttrib::get_class_slot());
   _inv_state_mask.clear_bit(DepthWriteAttrib::get_class_slot());
@@ -1013,10 +1023,26 @@ reset() {
   if (is_at_least_gl_version(4, 4) || has_extension("GL_ARB_clear_texture")) {
     _glClearTexImage = (PFNGLCLEARTEXIMAGEPROC)
       get_extension_func("glClearTexImage");
+    _glClearTexSubImage = (PFNGLCLEARTEXSUBIMAGEPROC)
+      get_extension_func("glClearTexSubImage");
+
+    if (_glClearTexImage == nullptr || _glClearTexSubImage == nullptr) {
+      GLCAT.warning()
+        << "GL_ARB_clear_texture advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
+    } else {
+      _supports_clear_texture = true;
+    }
+  }
+#elif !defined(OPENGLES_1)
+  if (has_extension("GL_EXT_clear_texture")) {
+    _glClearTexImage = (PFNGLCLEARTEXIMAGEEXTPROC)
+      get_extension_func("glClearTexImageEXT");
+    _glClearTexSubImage = (PFNGLCLEARTEXSUBIMAGEEXTPROC)
+      get_extension_func("glClearTexSubImageEXT");
 
-    if (_glClearTexImage == nullptr) {
+    if (_glClearTexImage == nullptr || _glClearTexSubImage == nullptr) {
       GLCAT.warning()
-        << "GL_ARB_clear_texture advertised as supported by OpenGL runtime, but could not get pointers to extension function.\n";
+        << "GL_EXT_clear_texture advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
     } else {
       _supports_clear_texture = true;
     }
@@ -2226,6 +2252,9 @@ reset() {
   // Check for SSBOs.
   if (is_at_least_gl_version(4, 3) || has_extension("ARB_shader_storage_buffer_object")) {
     _supports_shader_buffers = _supports_program_interface_query;
+
+    _glShaderStorageBlockBinding = (PFNGLSHADERSTORAGEBLOCKBINDINGPROC)
+       get_extension_func("glShaderStorageBlockBinding");
   } else
 #endif
   {
@@ -3383,23 +3412,30 @@ reset() {
 #endif
 
   // Set depth range from zero to one if requested.
-#ifndef OPENGLES
+#ifndef OPENGLES_1
   _use_depth_zero_to_one = false;
   _use_remapped_depth_range = false;
 
   if (gl_depth_zero_to_one) {
+#ifndef OPENGLES
+    PFNGLCLIPCONTROLPROC pglClipControl = nullptr;
     if (is_at_least_gl_version(4, 5) || has_extension("GL_ARB_clip_control")) {
-      PFNGLCLIPCONTROLPROC pglClipControl =
-        (PFNGLCLIPCONTROLPROC)get_extension_func("glClipControl");
+      pglClipControl = (PFNGLCLIPCONTROLPROC)get_extension_func("glClipControl");
+    }
+#else
+    PFNGLCLIPCONTROLEXTPROC pglClipControl = nullptr;
+    if (has_extension("GL_EXT_clip_control")) {
+      pglClipControl = (PFNGLCLIPCONTROLEXTPROC)get_extension_func("glClipControlEXT");
+    }
+#endif
 
-      if (pglClipControl != nullptr) {
-        pglClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
-        _use_depth_zero_to_one = true;
+    if (pglClipControl != nullptr) {
+      pglClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
+      _use_depth_zero_to_one = true;
 
-        if (GLCAT.is_debug()) {
-          GLCAT.debug()
-            << "Set zero-to-one depth using glClipControl\n";
-        }
+      if (GLCAT.is_debug()) {
+        GLCAT.debug()
+          << "Set zero-to-one depth using glClipControl\n";
       }
     }/* else if (has_extension("GL_NV_depth_buffer_float")) {
       // Alternatively, all GeForce 8+ and even some AMD drivers support this
@@ -3411,6 +3447,7 @@ reset() {
         _glDepthRangedNV(-1.0, 1.0);
         _use_depth_zero_to_one = true;
         _use_remapped_depth_range = true;
+        _has_attrib_depth_range = false;
 
         if (GLCAT.is_debug()) {
           GLCAT.debug()
@@ -3426,6 +3463,32 @@ reset() {
   }
 #endif
 
+  if (_has_attrib_depth_range) {
+#ifdef OPENGLES
+    glDepthRangef(0.0f, 1.0f);
+#else
+    glDepthRange(0.0, 1.0);
+#endif
+    _depth_range_near = 0;
+    _depth_range_far = 1;
+    _has_attrib_depth_range = false;
+  }
+
+#ifndef OPENGLES_1
+#ifndef OPENGLES
+  if (is_at_least_gl_version(4, 6) || has_extension("GL_ARB_polygon_offset_clamp")) {
+    _glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPEXTPROC)get_extension_func("glPolygonOffsetClamp");
+  }
+  else
+#endif
+  if (has_extension("GL_EXT_polygon_offset_clamp")) {
+    _glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPEXTPROC)get_extension_func("glPolygonOffsetClampEXT");
+  }
+  else {
+    _glPolygonOffsetClamp = null_glPolygonOffsetClamp;
+  }
+#endif
+
   // Set up all the enableddisabled flags to GL's known initial values:
   // everything off.
   _multisample_mode = 0;
@@ -4016,6 +4079,33 @@ prepare_display_region(DisplayRegionPipelineReader *dr) {
     }
   }
 
+  PN_stdfloat nearv;
+  PN_stdfloat farv;
+  dr->get_depth_range(nearv, farv);
+#ifdef GSG_VERBOSE
+  if (GLCAT.is_spam()) {
+    GLCAT.spam()
+      << "glDepthRange(" << nearv << ", " << farv << ")" << endl;
+  }
+#endif
+
+#ifdef OPENGLES
+  // OpenGL ES uses a single-precision call.
+  glDepthRangef((GLclampf)nearv, (GLclampf)farv);
+#else
+  // Mainline OpenGL uses a double-precision call.
+  if (!_use_remapped_depth_range) {
+    glDepthRange((GLclampd)nearv, (GLclampd)farv);
+  } else {
+    // If we have a remapped depth range, we should adjust the values to range
+    // from -1 to 1.  We need to use an NV extension to pass unclamped values.
+    _glDepthRangedNV(nearv * 2.0 - 1.0, farv * 2.0 - 1.0);
+  }
+#endif  // OPENGLES
+  _has_attrib_depth_range = false;
+  _depth_range_near = nearv;
+  _depth_range_far = farv;
+
   report_my_gl_errors();
 }
 
@@ -8172,42 +8262,68 @@ do_issue_fog() {
  *
  */
 void CLP(GraphicsStateGuardian)::
-do_issue_depth_offset() {
-  const DepthOffsetAttrib *target_depth_offset = (const DepthOffsetAttrib *)
-     _target_rs->get_attrib_def(DepthOffsetAttrib::get_class_slot());
-
+do_issue_depth_bias() {
+  const DepthOffsetAttrib *target_depth_offset;
+  _target_rs->get_attrib_def(target_depth_offset);
   int offset = target_depth_offset->get_offset();
 
-  if (offset != 0) {
+  const DepthBiasAttrib *target_depth_bias;
+  if (_target_rs->get_attrib(target_depth_bias)) {
+    GLfloat slope_factor = target_depth_bias->get_slope_factor();
+    GLfloat constant_factor = target_depth_bias->get_constant_factor();
+
+    slope_factor -= offset;
+    constant_factor -= offset;
+
+#ifndef OPENGLES_1
+    GLfloat clamp = target_depth_bias->get_clamp();
+    _glPolygonOffsetClamp(slope_factor, constant_factor, clamp);
+#else
+    glPolygonOffset(slope_factor, constant_factor);
+#endif
+    enable_polygon_offset(true);
+  }
+  else if (offset != 0) {
     // The relationship between these two parameters is a little unclear and
     // poorly explained in the GL man pages.
     glPolygonOffset((GLfloat) -offset, (GLfloat) -offset);
     enable_polygon_offset(true);
-
-  } else {
+  }
+  else {
     enable_polygon_offset(false);
   }
 
   PN_stdfloat min_value = target_depth_offset->get_min_value();
   PN_stdfloat max_value = target_depth_offset->get_max_value();
+  if (min_value != (PN_stdfloat)0.0 ||
+      max_value != (PN_stdfloat)1.0 ||
+      _has_attrib_depth_range) {
+    min_value = _depth_range_far * min_value + _depth_range_near * (1 - min_value);
+    max_value = _depth_range_far * max_value + _depth_range_near * (1 - max_value);
+
 #ifdef GSG_VERBOSE
-    GLCAT.spam()
-      << "glDepthRange(" << min_value << ", " << max_value << ")" << endl;
+    if (GLCAT.is_spam()) {
+      GLCAT.spam()
+        << "glDepthRange(" << min_value << ", " << max_value << ")" << endl;
+    }
 #endif
 #ifdef OPENGLES
-  // OpenGL ES uses a single-precision call.
-  glDepthRangef((GLclampf)min_value, (GLclampf)max_value);
+    // OpenGL ES uses a single-precision call.
+    glDepthRangef((GLclampf)min_value, (GLclampf)max_value);
 #else
-  // Mainline OpenGL uses a double-precision call.
-  if (!_use_remapped_depth_range) {
-    glDepthRange((GLclampd)min_value, (GLclampd)max_value);
-  } else {
-    // If we have a remapped depth range, we should adjust the values to range
-    // from -1 to 1.  We need to use an NV extension to pass unclamped values.
-    _glDepthRangedNV(min_value * 2.0 - 1.0, max_value * 2.0 - 1.0);
-  }
+    // Mainline OpenGL uses a double-precision call.
+    if (!_use_remapped_depth_range) {
+      glDepthRange((GLclampd)min_value, (GLclampd)max_value);
+    } else {
+      // If we have a remapped depth range, we should adjust the values to range
+      // from -1 to 1.  We need to use an NV extension to pass unclamped values.
+      _glDepthRangedNV(min_value * 2.0 - 1.0, max_value * 2.0 - 1.0);
+    }
 #endif  // OPENGLES
 
+    _has_attrib_depth_range = true;
+  }
+
   report_my_gl_errors();
 }
 
@@ -10066,7 +10182,8 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
   bool is_3d = (texture_type == Texture::TT_3d_texture ||
                 texture_type == Texture::TT_2d_texture_array);
 
-  if (get_supports_compressed_texture_format(compression)) {
+  if (get_supports_compressed_texture_format(compression) &&
+      texture_type != Texture::TT_buffer_texture) {
     switch (compression) {
     case Texture::CM_on:
       // The user asked for just generic compression.  OpenGL supports
@@ -11782,11 +11899,15 @@ set_state_and_transform(const RenderState *target,
     _state_mask.set_bit(cull_face_slot);
   }
 
+  int depth_bias_slot = DepthBiasAttrib::get_class_slot();
   int depth_offset_slot = DepthOffsetAttrib::get_class_slot();
-  if (_target_rs->get_attrib(depth_offset_slot) != _state_rs->get_attrib(depth_offset_slot) ||
+  if (_target_rs->get_attrib(depth_bias_slot) != _state_rs->get_attrib(depth_bias_slot) ||
+      _target_rs->get_attrib(depth_offset_slot) != _state_rs->get_attrib(depth_offset_slot) ||
+      !_state_mask.get_bit(depth_bias_slot) ||
       !_state_mask.get_bit(depth_offset_slot)) {
     // PStatGPUTimer timer(this, _draw_set_state_depth_offset_pcollector);
-    do_issue_depth_offset();
+    do_issue_depth_bias();
+    _state_mask.set_bit(depth_bias_slot);
     _state_mask.set_bit(depth_offset_slot);
   }
 
@@ -13078,20 +13199,22 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
     image = tex->get_uncompressed_ram_image();
   }
 
+  Texture::TextureType texture_type = tex->get_texture_type();
   Texture::CompressionMode image_compression;
   if (image.is_null()) {
     image_compression = Texture::CM_off;
   } else {
     image_compression = tex->get_ram_image_compression();
-  }
 
-  if (!get_supports_compressed_texture_format(image_compression)) {
-    image = tex->get_uncompressed_ram_image();
-    image_compression = Texture::CM_off;
+    if (texture_type == Texture::TT_buffer_texture ||
+        !get_supports_compressed_texture_format(image_compression)) {
+      image = tex->get_uncompressed_ram_image();
+      image_compression = Texture::CM_off;
 
-    // If this triggers, Panda cannot decompress the texture.  Compile with
-    // libsquish support or precompress the texture.
-    nassertr(!image.is_null(), false);
+      // If this triggers, Panda cannot decompress the texture.  Compile with
+      // libsquish support or precompress the texture.
+      nassertr(!image.is_null(), false);
+    }
   }
 
   int mipmap_bias = 0;
@@ -13103,7 +13226,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
   // If we'll use immutable texture storage, we have to pick a sized image
   // format.
   bool force_sized = (gl_immutable_texture_storage && _supports_tex_storage) ||
-                     (tex->get_texture_type() == Texture::TT_buffer_texture);
+                     (texture_type == Texture::TT_buffer_texture);
 
   GLint internal_format = get_internal_image_format(tex, force_sized);
   GLint external_format = get_external_image_format(tex);
@@ -13132,7 +13255,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
   int max_dimension_y;
   int max_dimension_z;
 
-  switch (tex->get_texture_type()) {
+  switch (texture_type) {
   case Texture::TT_3d_texture:
     max_dimension_x = _max_3d_texture_dimension;
     max_dimension_y = _max_3d_texture_dimension;
@@ -13237,7 +13360,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
 
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
-  GLenum target = get_texture_target(tex->get_texture_type());
+  GLenum target = get_texture_target(texture_type);
   uses_mipmaps = (uses_mipmaps && !gl_ignore_mipmaps) || gl_force_mipmaps;
 #ifndef OPENGLES
   if (target == GL_TEXTURE_BUFFER) {
@@ -13413,7 +13536,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
           << ", uses_mipmaps = " << uses_mipmaps << "\n";
       }
 
-      switch (tex->get_texture_type()) {
+      switch (texture_type) {
       case Texture::TT_buffer_texture:
         // Won't get here, but squelch compiler warning
       case Texture::TT_1d_texture:
@@ -13460,59 +13583,9 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
     }
   }
 
-  bool success = true;
-  if (tex->get_texture_type() == Texture::TT_cube_map) {
-    // A cube map must load six different 2-d images (which are stored as the
-    // six pages of the system ram image).
-    if (!_supports_cube_map) {
-      report_my_gl_errors();
-      return false;
-    }
-    nassertr(target == GL_TEXTURE_CUBE_MAP, false);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
-       internal_format, external_format, component_type,
-       true, 0, image_compression);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
-       internal_format, external_format, component_type,
-       true, 1, image_compression);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
-       internal_format, external_format, component_type,
-       true, 2, image_compression);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
-       internal_format, external_format, component_type,
-       true, 3, image_compression);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
-       internal_format, external_format, component_type,
-       true, 4, image_compression);
-
-    success = success && upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias,
-       GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
-       internal_format, external_format, component_type,
-       true, 5, image_compression);
-
-  } else {
-    // Any other kind of texture can be loaded all at once.
-    success = upload_texture_image
-      (gtc, needs_reload, uses_mipmaps, mipmap_bias, target,
-       target, internal_format, external_format,
-       component_type, false, 0, image_compression);
-  }
+  bool success = upload_texture_image
+    (gtc, needs_reload, uses_mipmaps, mipmap_bias, target,
+     internal_format, external_format, component_type, image_compression);
 
   if (gtc->_generate_mipmaps && _glGenerateMipmap != nullptr &&
       !image.is_null()) {
@@ -13573,21 +13646,13 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
 /**
  * Loads a texture image, or one page of a cube map image, from system RAM to
  * texture memory.
- *
- * texture_target is normally the same thing as page_target; both represent
- * the GL target onto which the texture image is loaded, e.g.  GL_TEXTURE_1D,
- * GL_TEXTURE_2D, etc.  The only time they may differ is in the case of cube
- * mapping, in which case texture_target will be target for the overall
- * texture, e.g.  GL_TEXTURE_CUBE_MAP, and page_target will be the target for
- * this particular page, e.g.  GL_TEXTURE_CUBE_MAP_POSITIVE_X.
  */
 bool CLP(GraphicsStateGuardian)::
 upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
                      bool uses_mipmaps, int mipmap_bias,
-                     GLenum texture_target, GLenum page_target,
+                     GLenum texture_target,
                      GLint internal_format,
                      GLint external_format, GLenum component_type,
-                     bool one_page_only, int z,
                      Texture::CompressionMode image_compression) {
   // Make sure the error stack is cleared out before we begin.
   clear_my_gl_errors();
@@ -13637,29 +13702,32 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
     // saving on texture memory fragmentation.
 
     if (GLCAT.is_debug()) {
+      SparseArray pages = gtc->get_image_modified_pages(0);
       if (num_ram_mipmap_levels == 0) {
         if (tex->has_clear_color()) {
           GLCAT.debug()
             << "clearing texture " << tex->get_name() << ", "
-            << width << " x " << height << " x " << depth << ", z = " << z
+            << width << " x " << height << " x " << depth << ", pages " << pages
             << ", uses_mipmaps = " << uses_mipmaps << ", clear_color = "
             << tex->get_clear_color() << "\n";
         } else {
           GLCAT.debug()
             << "not loading NULL image for texture " << tex->get_name()
             << ", " << width << " x " << height << " x " << depth
-            << ", z = " << z << ", uses_mipmaps = " << uses_mipmaps << "\n";
+            << ", pages " << pages << ", uses_mipmaps = " << uses_mipmaps << "\n";
         }
       } else {
         GLCAT.debug()
           << "updating image data of texture " << tex->get_name()
           << ", " << width << " x " << height << " x " << depth
-          << ", z = " << z << ", mipmaps " << num_ram_mipmap_levels
+          << ", pages " << pages << ", mipmaps " << num_ram_mipmap_levels
           << ", uses_mipmaps = " << uses_mipmaps << "\n";
       }
     }
 
     for (int n = mipmap_bias; n < num_levels; ++n) {
+      SparseArray pages = gtc->get_image_modified_pages(n);
+
       // we grab the mipmap pointer first, if it is NULL we grab the normal
       // mipmap image pointer which is a PTA_uchar
       const unsigned char *image_ptr = (unsigned char*)tex->get_ram_mipmap_pointer(n);
@@ -13686,8 +13754,17 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
                 // function.
                 vector_uchar clear_data = tex->get_clear_data();
 
-                _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
-                                 component_type, (void *)&clear_data[0]);
+                if (pages.has_all_of(0, depth)) {
+                  _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
+                                   component_type, (void *)&clear_data[0]);
+                }
+                else for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) {
+                  int begin = pages.get_subrange_begin(sri);
+                  int num_pages = pages.get_subrange_end(sri) - begin;
+                  _glClearTexSubImage(gtc->_index, n - mipmap_bias, 0, 0, begin,
+                                      width, height, num_pages, external_format,
+                                      component_type, (void *)&clear_data[0]);
+                }
                 continue;
               }
             } else {
@@ -13716,14 +13793,11 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       }
 
       PTA_uchar bgr_image;
-      size_t view_size = tex->get_ram_mipmap_view_size(n);
+      size_t page_size = tex->get_ram_mipmap_page_size(n);
       if (image_ptr != nullptr) {
         const unsigned char *orig_image_ptr = image_ptr;
+        size_t view_size = tex->get_ram_mipmap_view_size(n);
         image_ptr += view_size * gtc->get_view();
-        if (one_page_only) {
-          view_size = tex->get_ram_mipmap_page_size(n);
-          image_ptr += view_size * z;
-        }
         nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
         if (image_compression == Texture::CM_off) {
@@ -13736,23 +13810,29 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 
       int width = tex->get_expected_mipmap_x_size(n);
       int height = tex->get_expected_mipmap_y_size(n);
-#ifndef OPENGLES_1
-      int depth = tex->get_expected_mipmap_z_size(n);
-#endif
 
 #ifdef DO_PSTATS
-      _data_transferred_pcollector.add_level(view_size);
+      _data_transferred_pcollector.add_level(page_size * pages.get_num_on_bits());
 #endif
       switch (texture_target) {
 #ifndef OPENGLES_1
       case GL_TEXTURE_3D:
         if (_supports_3d_texture) {
-          if (image_compression == Texture::CM_off) {
-            _glTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                             external_format, component_type, image_ptr);
-          } else {
-            _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                                       external_format, view_size, image_ptr);
+          for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) {
+            int begin = pages.get_subrange_begin(sri);
+            int num_pages = pages.get_subrange_end(sri) - begin;
+            const unsigned char *page_ptr = image_ptr + page_size * begin;
+
+            if (image_compression == Texture::CM_off) {
+              _glTexSubImage3D(texture_target, n - mipmap_bias,
+                               0, 0, begin, width, height, num_pages,
+                               external_format, component_type, page_ptr);
+            } else {
+              _glCompressedTexSubImage3D(texture_target, n - mipmap_bias,
+                                         0, 0, begin, width, height, num_pages,
+                                         external_format,
+                                         page_size * num_pages, page_ptr);
+            }
           }
         } else {
           report_my_gl_errors();
@@ -13764,11 +13844,11 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 #ifndef OPENGLES
       case GL_TEXTURE_1D:
         if (image_compression == Texture::CM_off) {
-          glTexSubImage1D(page_target, n - mipmap_bias, 0, width,
+          glTexSubImage1D(texture_target, n - mipmap_bias, 0, width,
                           external_format, component_type, image_ptr);
         } else {
-          _glCompressedTexSubImage1D(page_target, n - mipmap_bias, 0, width,
-                                     external_format, view_size, image_ptr);
+          _glCompressedTexSubImage1D(texture_target, n - mipmap_bias, 0, width,
+                                     external_format, page_size, image_ptr);
         }
         break;
 #endif  // OPENGLES
@@ -13777,12 +13857,21 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       case GL_TEXTURE_2D_ARRAY:
       case GL_TEXTURE_CUBE_MAP_ARRAY:
         if (_supports_2d_texture_array) {
-          if (image_compression == Texture::CM_off) {
-            _glTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                             external_format, component_type, image_ptr);
-          } else {
-            _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                                       external_format, view_size, image_ptr);
+          for (size_t sri = 0; sri < pages.get_num_subranges(); ++sri) {
+            int begin = pages.get_subrange_begin(sri);
+            int num_pages = pages.get_subrange_end(sri) - begin;
+            const unsigned char *page_ptr = image_ptr + page_size * begin;
+
+            if (image_compression == Texture::CM_off) {
+              _glTexSubImage3D(texture_target, n - mipmap_bias,
+                               0, 0, begin, width, height, num_pages,
+                               external_format, component_type, page_ptr);
+            } else {
+              _glCompressedTexSubImage3D(texture_target, n - mipmap_bias,
+                                         0, 0, begin, width, height, num_pages,
+                                         external_format,
+                                         page_size * num_pages, page_ptr);
+            }
           }
         } else {
           report_my_gl_errors();
@@ -13794,7 +13883,7 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 #ifndef OPENGLES
       case GL_TEXTURE_BUFFER:
         if (_supports_buffer_texture) {
-          _glBufferSubData(GL_TEXTURE_BUFFER, 0, view_size, image_ptr);
+          _glBufferSubData(GL_TEXTURE_BUFFER, 0, page_size, image_ptr);
         } else {
           report_my_gl_errors();
           return false;
@@ -13802,18 +13891,46 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
         break;
 #endif  // OPENGLES
 
+      case GL_TEXTURE_CUBE_MAP:
+        if (_supports_cube_map) {
+          // This is the only texture type that must be specified using separate
+          // per-page calls.
+          if (n == 0) {
+            height = tex->get_y_size() - tex->get_pad_y_size();
+          }
+          for (int z = 0; z < 6; ++z) {
+            if (pages.get_bit(z)) {
+              GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z;
+              const unsigned char *page_ptr = image_ptr + page_size * z;
+
+              if (image_compression == Texture::CM_off) {
+                glTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
+                                external_format, component_type, page_ptr);
+              } else {
+                _glCompressedTexSubImage2D(page_target, n - mipmap_bias,
+                                           0, 0, width, height,
+                                           external_format, page_size, page_ptr);
+              }
+            }
+          }
+        } else {
+          report_my_gl_errors();
+          return false;
+        }
+        break;
+
       default:
         if (image_compression == Texture::CM_off) {
-          if (n==0) {
+          if (n == 0) {
             // It's unfortunate that we can't adjust the width, too, but
             // TexSubImage2D doesn't accept a row-stride parameter.
             height = tex->get_y_size() - tex->get_pad_y_size();
           }
-          glTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
+          glTexSubImage2D(texture_target, n - mipmap_bias, 0, 0, width, height,
                           external_format, component_type, image_ptr);
         } else {
-          _glCompressedTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
-                                     external_format, view_size, image_ptr);
+          _glCompressedTexSubImage2D(texture_target, n - mipmap_bias, 0, 0, width, height,
+                                     external_format, page_size, image_ptr);
         }
         break;
       }
@@ -13837,7 +13954,7 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
     if (GLCAT.is_debug()) {
       GLCAT.debug()
         << "loading new texture object for " << tex->get_name() << ", " << width
-        << " x " << height << " x " << depth << ", z = " << z << ", mipmaps "
+        << " x " << height << " x " << depth << ", mipmaps "
         << num_ram_mipmap_levels << ", uses_mipmaps = " << uses_mipmaps << "\n";
     }
 
@@ -13902,10 +14019,6 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       if (image_ptr != nullptr) {
         const unsigned char *orig_image_ptr = image_ptr;
         image_ptr += view_size * gtc->get_view();
-        if (one_page_only) {
-          view_size = tex->get_ram_mipmap_page_size(n);
-          image_ptr += view_size * z;
-        }
         nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
         if (image_compression == Texture::CM_off) {
@@ -13929,12 +14042,11 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
 #ifndef OPENGLES  // 1-d textures not supported by OpenGL ES.  Fall through.
       case GL_TEXTURE_1D:
         if (image_compression == Texture::CM_off) {
-          glTexImage1D(page_target, n - mipmap_bias, internal_format,
-                       width, 0,
-                       external_format, component_type, image_ptr);
+          glTexImage1D(texture_target, n - mipmap_bias, internal_format,
+                       width, 0, external_format, component_type, image_ptr);
         } else {
-          _glCompressedTexImage1D(page_target, n - mipmap_bias, external_format, width,
-                                  0, view_size, image_ptr);
+          _glCompressedTexImage1D(texture_target, n - mipmap_bias, external_format,
+                                  width, 0, view_size, image_ptr);
         }
         break;
 #endif  // OPENGLES  // OpenGL ES will fall through.
@@ -13943,13 +14055,12 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       case GL_TEXTURE_3D:
         if (_supports_3d_texture) {
           if (image_compression == Texture::CM_off) {
-            _glTexImage3D(page_target, n - mipmap_bias, internal_format,
+            _glTexImage3D(texture_target, n - mipmap_bias, internal_format,
                           width, height, depth, 0,
                           external_format, component_type, image_ptr);
           } else {
-            _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
-                                    height, depth,
-                                    0, view_size, image_ptr);
+            _glCompressedTexImage3D(texture_target, n - mipmap_bias, external_format,
+                                    width, height, depth, 0, view_size, image_ptr);
           }
         } else {
           report_my_gl_errors();
@@ -13963,13 +14074,12 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       case GL_TEXTURE_CUBE_MAP_ARRAY:
         if (_supports_2d_texture_array) {
           if (image_compression == Texture::CM_off) {
-            _glTexImage3D(page_target, n - mipmap_bias, internal_format,
+            _glTexImage3D(texture_target, n - mipmap_bias, internal_format,
                           width, height, depth, 0,
                           external_format, component_type, image_ptr);
           } else {
-            _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
-                                    height, depth,
-                                    0, view_size, image_ptr);
+            _glCompressedTexImage3D(texture_target, n - mipmap_bias, external_format,
+                                    width, height, depth, 0, view_size, image_ptr);
           }
         } else {
           report_my_gl_errors();
@@ -13990,13 +14100,37 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
         break;
 #endif  // OPENGLES
 
+      case GL_TEXTURE_CUBE_MAP:
+        if (_supports_cube_map) {
+          // This is the only texture type that must be specified using separate
+          // per-page calls.
+          size_t page_size = tex->get_ram_mipmap_page_size(n);
+          for (int z = 0; z < 6; ++z) {
+            GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z;
+            const unsigned char *page_ptr = image_ptr + page_size * z;
+
+            if (image_compression == Texture::CM_off) {
+              glTexImage2D(page_target, n - mipmap_bias, internal_format,
+                           width, height, 0,
+                           external_format, component_type, page_ptr);
+            } else {
+              _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format,
+                                      width, height, 0, page_size, page_ptr);
+            }
+          }
+        } else {
+          report_my_gl_errors();
+          return false;
+        }
+        break;
+
       default:
         if (image_compression == Texture::CM_off) {
-          glTexImage2D(page_target, n - mipmap_bias, internal_format,
+          glTexImage2D(texture_target, n - mipmap_bias, internal_format,
                        width, height, 0,
                        external_format, component_type, image_ptr);
         } else {
-          _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format,
+          _glCompressedTexImage2D(texture_target, n - mipmap_bias, external_format,
                                   width, height, 0, view_size, image_ptr);
         }
       }

+ 16 - 8
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -247,10 +247,10 @@ typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFo
 typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params);
 typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags);
 typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);
-#endif  // OPENGLES_1
-#ifndef OPENGLES
 typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
 typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
+#endif  // OPENGLES_1
+#ifndef OPENGLES
 typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures);
 typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers);
 typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures);
@@ -471,7 +471,7 @@ protected:
 #ifdef SUPPORT_FIXED_FUNCTION
   void do_issue_fog();
 #endif
-  void do_issue_depth_offset();
+  void do_issue_depth_bias();
   void do_issue_shade_model();
 #ifndef OPENGLES_1
   void do_issue_shader();
@@ -627,10 +627,9 @@ protected:
   bool upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps);
   bool upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
                             bool uses_mipmaps, int mipmap_bias,
-                            GLenum texture_target, GLenum page_target,
+                            GLenum texture_target,
                             GLint internal_format, GLint external_format,
                             GLenum component_type,
-                            bool one_page_only, int z,
                             Texture::CompressionMode image_compression);
   void generate_mipmaps(CLP(TextureContext) *gtc);
   bool upload_simple_texture(CLP(TextureContext) *gtc);
@@ -687,6 +686,9 @@ protected:
   bool _scissor_enabled;
   bool _scissor_attrib_active;
   epvector<LVecBase4i> _scissor_array;
+  PN_stdfloat _depth_range_near;
+  PN_stdfloat _depth_range_far;
+  bool _has_attrib_depth_range;
 
 #ifndef OPENGLES_1
   BitMask32 _enabled_vertex_attrib_arrays;
@@ -725,7 +727,6 @@ protected:
   typedef pmap<NodePath, DirectionalLightFrameData> DirectionalLights;
   DirectionalLights _dlights;
 
-  int _pass_number;
   GLuint _geom_display_list;
   GLuint _current_vbuffer_index;
   GLuint _current_ibuffer_index;
@@ -769,11 +770,16 @@ protected:
 #endif
 
 public:
-#ifndef OPENGLES
+#ifndef OPENGLES_1
   bool _use_depth_zero_to_one;
   bool _use_remapped_depth_range;
+#endif
+#ifndef OPENGLES
   PFNGLDEPTHRANGEDNVPROC _glDepthRangedNV;
 #endif
+#ifndef OPENGLES_1
+  PFNGLPOLYGONOFFSETCLAMPEXTPROC _glPolygonOffsetClamp;
+#endif
 
   bool _supports_point_parameters;
   PFNGLPOINTPARAMETERFVPROC _glPointParameterfv;
@@ -810,8 +816,9 @@ public:
 #endif
 
   bool _supports_clear_texture;
-#ifndef OPENGLES
+#ifndef OPENGLES_1
   PFNGLCLEARTEXIMAGEPROC _glClearTexImage;
+  PFNGLCLEARTEXSUBIMAGEPROC _glClearTexSubImage;
 #endif
 
   bool _supports_clear_buffer;
@@ -1087,6 +1094,7 @@ public:
   PFNGLMAKETEXTUREHANDLENONRESIDENTPROC _glMakeTextureHandleNonResident;
   PFNGLUNIFORMHANDLEUI64PROC _glUniformHandleui64;
   PFNGLUNIFORMHANDLEUI64VPROC _glUniformHandleui64v;
+  PFNGLSHADERSTORAGEBLOCKBINDINGPROC _glShaderStorageBlockBinding;
 #endif  // !OPENGLES
 
 #ifndef OPENGLES_1

+ 16 - 0
panda/src/glstuff/glShaderContext_src.cxx

@@ -305,6 +305,8 @@ reflect_program() {
     block_maxlength = max(64, block_maxlength);
     char *block_name_cstr = (char *)alloca(block_maxlength);
 
+    BitArray bindings;
+
     for (int i = 0; i < block_count; ++i) {
       block_name_cstr[0] = 0;
       _glgsg->_glGetProgramResourceName(_glsl_program, GL_SHADER_STORAGE_BLOCK, i, block_maxlength, nullptr, block_name_cstr);
@@ -313,6 +315,20 @@ reflect_program() {
       GLint values[2];
       _glgsg->_glGetProgramResourceiv(_glsl_program, GL_SHADER_STORAGE_BLOCK, i, 2, props, 2, nullptr, values);
 
+      if (bindings.get_bit(values[0])) {
+        // Binding index already in use, assign a different one.
+        values[0] = bindings.get_lowest_off_bit();
+        _glgsg->_glShaderStorageBlockBinding(_glsl_program, i, values[0]);
+      }
+      bindings.set_bit(values[0]);
+
+      if (GLCAT.is_debug()) {
+        GLCAT.debug()
+          << "Active shader storage block " << block_name_cstr
+          << " with size " << values[1] << " is bound to binding "
+          << values[0] << "\n";
+      }
+
       StorageBlock block;
       block._name = InternalName::make(block_name_cstr);
       block._binding_index = values[0];

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

@@ -805,6 +805,11 @@ make_lines_in_place() {
 #endif
   }
 
+  if (cdata->_primitive_type == PT_polygons ||
+      cdata->_primitive_type == PT_patches) {
+    cdata->_primitive_type = PT_lines;
+  }
+
   cdata->_modified = Geom::get_next_modified();
   reset_geom_rendering(cdata);
   clear_cache_stage(current_thread);
@@ -842,6 +847,10 @@ make_points_in_place() {
 #endif
   }
 
+  if (cdata->_primitive_type != PT_none) {
+    cdata->_primitive_type = PT_points;
+  }
+
   cdata->_modified = Geom::get_next_modified();
   reset_geom_rendering(cdata);
   clear_cache_stage(current_thread);
@@ -879,6 +888,10 @@ make_patches_in_place() {
 #endif
   }
 
+  if (cdata->_primitive_type != PT_none) {
+    cdata->_primitive_type = PT_patches;
+  }
+
   cdata->_modified = Geom::get_next_modified();
   reset_geom_rendering(cdata);
   clear_cache_stage(current_thread);

+ 6 - 7
panda/src/gobj/texture.I

@@ -357,8 +357,6 @@ INLINE bool Texture::
 load(const PNMImage &pnmimage, const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   do_clear(cdata);
-  cdata->inc_properties_modified();
-  cdata->inc_image_modified();
   if (do_load_one(cdata, pnmimage, get_name(), 0, 0, options)) {
     bool generate_mipmaps = ((options.get_texture_flags() & LoaderOptions::TF_generate_mipmaps) != 0);
     consider_auto_process_ram_image(generate_mipmaps || uses_mipmaps(), true);
@@ -374,7 +372,7 @@ INLINE bool Texture::
 load(const PNMImage &pnmimage, int z, int n, const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   cdata->inc_properties_modified();
-  cdata->inc_image_modified();
+  cdata->inc_image_page_modified(z);
   if (do_load_one(cdata, pnmimage, get_name(), z, n, options)) {
     return true;
   }
@@ -388,8 +386,6 @@ INLINE bool Texture::
 load(const PfmFile &pfm, const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   do_clear(cdata);
-  cdata->inc_properties_modified();
-  cdata->inc_image_modified();
   if (do_load_one(cdata, pfm, get_name(), 0, 0, options)) {
     bool generate_mipmaps = ((options.get_texture_flags() & LoaderOptions::TF_generate_mipmaps) != 0);
     consider_auto_process_ram_image(generate_mipmaps || uses_mipmaps(), true);
@@ -405,7 +401,7 @@ INLINE bool Texture::
 load(const PfmFile &pfm, int z, int n, const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   cdata->inc_properties_modified();
-  cdata->inc_image_modified();
+  cdata->inc_image_page_modified(z);
   if (do_load_one(cdata, pfm, get_name(), z, n, options)) {
     return true;
   }
@@ -2460,11 +2456,14 @@ inc_properties_modified() {
 }
 
 /**
- *
+ * Marks the whole image as modified.
  */
 INLINE void Texture::CData::
 inc_image_modified() {
   ++_image_modified;
+  _modified_pages.resize(1);
+  _modified_pages[0]._z_end = (size_t)-1;
+  _modified_pages[0]._modified = _image_modified;
 }
 
 /**

+ 101 - 11
panda/src/gobj/texture.cxx

@@ -551,8 +551,6 @@ bool Texture::
 read(const Filename &fullpath, const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   do_clear(cdata);
-  cdata->inc_properties_modified();
-  cdata->inc_image_modified();
   return do_read(cdata, fullpath, Filename(), 0, 0, 0, 0, false, false,
                  options, nullptr);
 }
@@ -570,8 +568,6 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
      const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   do_clear(cdata);
-  cdata->inc_properties_modified();
-  cdata->inc_image_modified();
   return do_read(cdata, fullpath, alpha_fullpath, primary_file_num_channels,
                  alpha_file_channel, 0, 0, false, false,
                  options, nullptr);
@@ -585,12 +581,15 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
  * the various parameters.
  */
 bool Texture::
-read(const Filename &fullpath, int z, int n,
-     bool read_pages, bool read_mipmaps,
+read(const Filename &fullpath, int z, int n, bool read_pages, bool read_mipmaps,
      const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   cdata->inc_properties_modified();
-  cdata->inc_image_modified();
+  if (read_pages) {
+    cdata->inc_image_modified();
+  } else {
+    cdata->inc_image_page_modified(z);
+  }
   return do_read(cdata, fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps,
                  options, nullptr);
 }
@@ -655,7 +654,11 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
      const LoaderOptions &options) {
   CDWriter cdata(_cycler, true);
   cdata->inc_properties_modified();
-  cdata->inc_image_modified();
+  if (read_pages) {
+    cdata->inc_image_modified();
+  } else {
+    cdata->inc_image_page_modified(z);
+  }
   return do_read(cdata, fullpath, alpha_fullpath, primary_file_num_channels,
                  alpha_file_channel, z, n, read_pages, read_mipmaps,
                  options, record);
@@ -1422,6 +1425,39 @@ peek() {
   return nullptr;
 }
 
+/**
+ * Returns a SparseArray containing all the image pages that have been modified
+ * since the given UpdateSeq value.
+ */
+SparseArray Texture::
+get_image_modified_pages(UpdateSeq since, int n) const {
+  CDReader cdata(_cycler);
+
+  SparseArray result;
+  if (since == cdata->_image_modified) {
+    // Early-out since no range is more recent than _image_modified.
+    return result;
+  }
+
+  if (n > 0 && cdata->_texture_type == Texture::TT_3d_texture) {
+    // Don't bother handling this special case, just consider all mipmap pages
+    // modified.
+    result.set_range(0, do_get_expected_mipmap_z_size(cdata, n));
+    return result;
+  }
+
+  for (const ModifiedPageRange &range : cdata->_modified_pages) {
+    if (range._z_begin >= cdata->_z_size) {
+      break;
+    }
+    if (since < range._modified) {
+      result.set_range(range._z_begin, std::min(range._z_end, (size_t)cdata->_z_size) - range._z_begin);
+    }
+  }
+
+  return result;
+}
+
 /**
  * Indicates that the texture should be enqueued to be prepared in the
  * indicated prepared_objects at the beginning of the next frame.  This will
@@ -3541,7 +3577,7 @@ do_load_sub_image(CData *cdata, const PNMImage &image, int x, int y, int z, int
   // Flip y
   y = cdata->_y_size - (image.get_y_size() + y);
 
-  cdata->inc_image_modified();
+  cdata->inc_image_page_modified(z);
   do_modify_ram_mipmap_image(cdata, n);
   convert_from_pnmimage(cdata->_ram_images[n]._image,
                         do_get_expected_ram_mipmap_page_size(cdata, n),
@@ -5866,7 +5902,12 @@ do_consider_auto_process_ram_image(CData *cdata, bool generate_mipmaps,
   if (allow_compression && !driver_compress_textures) {
     CompressionMode compression = cdata->_compression;
     if (compression == CM_default && compressed_textures) {
-      compression = CM_on;
+      if (cdata->_texture_type == Texture::TT_buffer_texture) {
+        compression = CM_off;
+      }
+      else {
+        compression = CM_on;
+      }
     }
     if (compression != CM_off && cdata->_ram_image_compression == CM_off) {
       GraphicsStateGuardianBase *gsg = GraphicsStateGuardianBase::get_default_gsg();
@@ -7249,7 +7290,11 @@ do_set_quality_level(CData *cdata, Texture::QualityLevel quality_level) {
 bool Texture::
 do_has_compression(const CData *cdata) const {
   if (cdata->_compression == CM_default) {
-    return compressed_textures;
+    if (cdata->_texture_type != Texture::TT_buffer_texture) {
+      return compressed_textures;
+    } else {
+      return false;
+    }
   } else {
     return (cdata->_compression != CM_off);
   }
@@ -10610,6 +10655,10 @@ CData() {
   _simple_ram_image._page_size = 0;
 
   _has_clear_color = false;
+
+  _modified_pages.resize(1);
+  _modified_pages[0]._z_end = (size_t)-1;
+  _modified_pages[0]._modified = _image_modified;
 }
 
 /**
@@ -10624,6 +10673,7 @@ CData(const Texture::CData &copy) {
   _properties_modified = copy._properties_modified;
   _image_modified = copy._image_modified;
   _simple_image_modified = copy._simple_image_modified;
+  _modified_pages = copy._modified_pages;
 }
 
 /**
@@ -10681,6 +10731,46 @@ do_assign(const Texture::CData *copy) {
   _simple_ram_image = copy->_simple_ram_image;
 }
 
+/**
+ * Marks a single page of the image as modified.
+ */
+void Texture::CData::
+inc_image_page_modified(int z) {
+  ++_image_modified;
+
+  ModifiedPageRanges::iterator it = _modified_pages.begin();
+  while (it != _modified_pages.end() && (*it)._z_end <= z) {
+    ++it;
+    continue;
+  }
+  nassertv(it != _modified_pages.end());
+
+  size_t orig_z_end = (*it)._z_end;
+  UpdateSeq orig_modified = (*it)._modified;
+
+  if (z > (*it)._z_begin) {
+    // Split prefix.
+    ModifiedPageRange copy(*it);
+    copy._z_end = z;
+    it = _modified_pages.insert(it, copy);
+    ++it;
+  }
+
+  (*it)._z_begin = z;
+  (*it)._z_end = z + 1;
+  (*it)._modified = _image_modified;
+
+  if (z + 1 < orig_z_end) {
+    // Split suffix.
+    ModifiedPageRange copy(*it);
+    copy._z_begin = z + 1;
+    copy._z_end = orig_z_end;
+    copy._modified = orig_modified;
+    ++it;
+    _modified_pages.insert(it, copy);
+  }
+}
+
 /**
  * Writes the contents of this object to the datagram for shipping out to a
  * Bam file.

+ 12 - 0
panda/src/gobj/texture.h

@@ -525,6 +525,8 @@ PUBLISHED:
   MAKE_PROPERTY(image_modified, get_image_modified);
   MAKE_PROPERTY(simple_image_modified, get_simple_image_modified);
 
+  SparseArray get_image_modified_pages(UpdateSeq since, int n = 0) const;
+
   INLINE bool has_auto_texture_scale() const;
   INLINE AutoTextureScale get_auto_texture_scale() const;
   INLINE void set_auto_texture_scale(AutoTextureScale scale);
@@ -932,6 +934,13 @@ private:
 protected:
   typedef pvector<RamImage> RamImages;
 
+  struct ModifiedPageRange {
+    size_t _z_begin = 0;
+    size_t _z_end;
+    UpdateSeq _modified;
+  };
+  typedef pvector<ModifiedPageRange> ModifiedPageRanges;
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_GOBJ CData : public CycleData {
   public:
@@ -949,6 +958,7 @@ protected:
     void do_assign(const CData *copy);
     INLINE void inc_properties_modified();
     INLINE void inc_image_modified();
+    void inc_image_page_modified(int z);
     INLINE void inc_simple_image_modified();
 
     Filename _filename;
@@ -1020,6 +1030,8 @@ protected:
     UpdateSeq _image_modified;
     UpdateSeq _simple_image_modified;
 
+    ModifiedPageRanges _modified_pages;
+
   public:
     static TypeHandle get_class_type() {
       return _type_handle;

+ 18 - 0
panda/src/gobj/textureContext.I

@@ -76,6 +76,15 @@ was_simple_image_modified() const {
   return _simple_image_modified != get_texture()->get_simple_image_modified();
 }
 
+/**
+ * Returns true if the given page of the texture image has been modified since
+ * the last time mark_loaded() was called.
+ */
+INLINE bool TextureContext::
+was_image_page_modified(int z, int n) const {
+  return get_texture()->get_image_modified_pages(_image_modified, n).get_bit(z);
+}
+
 /**
  * Returns a sequence number which is guaranteed to change at least every time
  * the texture properties (unrelated to the image) are modified.
@@ -103,6 +112,15 @@ get_simple_image_modified() const {
   return _simple_image_modified;
 }
 
+/**
+ * Returns a SparseArray indicating which pages of the texture have been
+ * modified since the last call to mark_loaded().
+ */
+INLINE SparseArray TextureContext::
+get_image_modified_pages(int n) const {
+  return get_texture()->get_image_modified_pages(_image_modified, n);
+}
+
 /**
  * Should be called (usually by a derived class) when the on-card size of this
  * object has changed.

+ 3 - 0
panda/src/gobj/textureContext.h

@@ -44,11 +44,14 @@ PUBLISHED:
   INLINE bool was_properties_modified() const;
   INLINE bool was_image_modified() const;
   INLINE bool was_simple_image_modified() const;
+  INLINE bool was_image_page_modified(int z, int n) const;
 
   INLINE UpdateSeq get_properties_modified() const;
   INLINE UpdateSeq get_image_modified() const;
   INLINE UpdateSeq get_simple_image_modified() const;
 
+  INLINE SparseArray get_image_modified_pages(int n = 0) const;
+
 public:
   INLINE void update_data_size_bytes(size_t new_data_size_bytes);
   INLINE void mark_loaded();

+ 0 - 1
panda/src/gobj/texturePeeker.h

@@ -89,7 +89,6 @@ private:
   int _y_size;
   int _z_size;
   int _is_cube;
-  int _unused1;
   int _pixel_width;
   Texture::Format _format;
   Texture::ComponentType _component_type;

+ 8 - 0
panda/src/linmath/lmatrix3_ext_src.I

@@ -34,6 +34,14 @@ __reduce__(PyObject *self) const {
   return result;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LMatrix3) Extension<FLOATNAME(LMatrix3)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}
+
 /**
  *
  */

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

@@ -19,6 +19,9 @@ template<>
 class Extension<FLOATNAME(LMatrix3)> : public ExtensionBase<FLOATNAME(LMatrix3)> {
 public:
   INLINE_LINMATH PyObject *__reduce__(PyObject *self) const;
+
+  INLINE_LINMATH FLOATNAME(LMatrix3) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lmatrix3_src.h

@@ -183,6 +183,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LMatrix3) &operator *= (FLOATTYPE scalar);
   INLINE_LINMATH FLOATNAME(LMatrix3) &operator /= (FLOATTYPE scalar);
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LMatrix3) __rmul__(FLOATTYPE scalar) const);
+
   INLINE_LINMATH void componentwise_mult(const FLOATNAME(LMatrix3) &other);
 
   INLINE_LINMATH FLOATTYPE determinant() const;

+ 8 - 0
panda/src/linmath/lmatrix4_ext_src.I

@@ -35,6 +35,14 @@ __reduce__(PyObject *self) const {
   return result;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LMatrix4) Extension<FLOATNAME(LMatrix4)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}
+
 /**
  *
  */

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

@@ -19,6 +19,9 @@ template<>
 class Extension<FLOATNAME(LMatrix4)> : public ExtensionBase<FLOATNAME(LMatrix4)> {
 public:
   INLINE_LINMATH PyObject *__reduce__(PyObject *self) const;
+
+  INLINE_LINMATH FLOATNAME(LMatrix4) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lmatrix4_src.h

@@ -189,6 +189,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LMatrix4) &operator *= (FLOATTYPE scalar);
   INLINE_LINMATH FLOATNAME(LMatrix4) &operator /= (FLOATTYPE scalar);
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LMatrix4) __rmul__(FLOATTYPE scalar) const);
+
   INLINE_LINMATH void componentwise_mult(const FLOATNAME(LMatrix4) &other);
 
   INLINE_LINMATH void transpose_from(const FLOATNAME(LMatrix4) &other);

+ 8 - 0
panda/src/linmath/lpoint2_ext_src.I

@@ -79,3 +79,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase2.
   return invoke_extension<FLOATNAME(LVecBase2)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LPoint2) Extension<FLOATNAME(LPoint2)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LPoint2)> : public ExtensionBase<FLOATNAME(LPoint2)> {
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LPoint2) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lpoint2_src.h

@@ -46,6 +46,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LPoint2) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LPoint2) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LPoint2) __rmul__(FLOATTYPE scalar) const);
+
 #ifndef FLOATTYPE_IS_INT
   INLINE_LINMATH FLOATNAME(LPoint2) normalized() const;
   INLINE_LINMATH FLOATNAME(LPoint2) project(const FLOATNAME(LVecBase2) &onto) const;

+ 8 - 0
panda/src/linmath/lpoint3_ext_src.I

@@ -80,3 +80,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase2.
   return invoke_extension<FLOATNAME(LVecBase3)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LPoint3) Extension<FLOATNAME(LPoint3)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LPoint3)> : public ExtensionBase<FLOATNAME(LPoint3)> {
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LPoint3) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lpoint3_src.h

@@ -66,6 +66,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LPoint3) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LPoint3) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LPoint3) __rmul__(FLOATTYPE scalar) const);
+
   // Some special named constructors for LPoint3.
 
   INLINE_LINMATH static const FLOATNAME(LPoint3) &origin(CoordinateSystem cs = CS_default);

+ 8 - 0
panda/src/linmath/lpoint4_ext_src.I

@@ -85,3 +85,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase4.
   return invoke_extension<FLOATNAME(LVecBase4)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LPoint4) Extension<FLOATNAME(LPoint4)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LPoint4)> : public ExtensionBase<FLOATNAME(LPoint4)> {
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LPoint4) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lpoint4_src.h

@@ -54,6 +54,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LPoint4) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LPoint4) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LPoint4) __rmul__(FLOATTYPE scalar) const);
+
 #ifndef FLOATTYPE_IS_INT
   INLINE_LINMATH FLOATNAME(LPoint4) normalized() const;
   INLINE_LINMATH FLOATNAME(LPoint4) project(const FLOATNAME(LVecBase4) &onto) const;

+ 59 - 0
panda/src/linmath/lvecBase2_ext_src.I

@@ -192,6 +192,25 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   return 0;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH PyObject *Extension<FLOATNAME(LVecBase2)>::
+__rmul__(PyObject *self, FLOATTYPE scalar) const {
+#ifndef CPPPARSER
+  extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
+#endif
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
+  if (py_vec != nullptr) {
+    FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
+    nassertr(vec != nullptr, nullptr);
+
+    vec->_v(0) = _this->_v(0) * scalar;
+    vec->_v(1) = _this->_v(1) * scalar;
+  }
+  return py_vec;
+}
+
 /**
  *
  */
@@ -338,5 +357,45 @@ __ceil__(PyObject *self) const {
   return py_vec;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH int Extension<FLOATNAME(LVecBase2)>::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+    PyErr_SetString(PyExc_BufferError,
+                    "Object is not writable.");
+    return -1;
+  }
+
+  static const char format[2] = {FLOATTOKEN, 0};
+  static const Py_ssize_t shape = FLOATNAME(LVecBase2)::num_components;
+
+  Py_INCREF(self);
+
+  view->buf = (void *)_this->get_data();
+  view->obj = self;
+  view->len = 2 * sizeof(FLOATTYPE);
+  view->readonly = 1;
+  view->itemsize = sizeof(FLOATTYPE);
+  view->format = nullptr;
+  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+    view->format = (char *)format;
+  }
+  view->ndim = 1;
+  view->shape = nullptr;
+  if ((flags & PyBUF_ND) == PyBUF_ND) {
+    view->shape = (Py_ssize_t *)&shape;
+  }
+  view->strides = nullptr;
+  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+    view->strides = &view->itemsize;
+  }
+  view->suboffsets = nullptr;
+  view->internal = nullptr;
+
+  return 0;
+}
+
 #undef PYNUMBER_FLOATTYPE
 #undef PY_AS_FLOATTYPE

+ 4 - 0
panda/src/linmath/lvecBase2_ext_src.h

@@ -23,6 +23,8 @@ public:
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
   INLINE_LINMATH std::string __repr__() const;
 
+  INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const;
+
   INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const;
   INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar);
 
@@ -32,6 +34,8 @@ public:
   INLINE_LINMATH PyObject *__round__(PyObject *self) const;
   INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
   INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+  INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
 };
 
 #include "lvecBase2_ext_src.I"

+ 4 - 0
panda/src/linmath/lvecBase2_src.h

@@ -133,6 +133,8 @@ PUBLISHED:
 
   INLINE_LINMATH void componentwise_mult(const FLOATNAME(LVecBase2) &other);
 
+  EXTENSION(INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const);
+
   EXTENSION(INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const);
   EXTENSION(INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar));
 
@@ -158,6 +160,8 @@ PUBLISHED:
   INLINE_LINMATH void write_datagram(Datagram &destination) const;
   INLINE_LINMATH void read_datagram(DatagramIterator &source);
 
+  EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
 public:
   // The underlying implementation is via the Eigen library, if available.
 

+ 61 - 0
panda/src/linmath/lvecBase3_ext_src.I

@@ -193,6 +193,27 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   return 0;
 }
 
+
+/**
+ *
+ */
+INLINE_LINMATH PyObject *Extension<FLOATNAME(LVecBase3)>::
+__rmul__(PyObject *self, FLOATTYPE scalar) const {
+#ifndef CPPPARSER
+  extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
+#endif
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
+  if (py_vec != nullptr) {
+    FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
+    nassertr(vec != nullptr, nullptr);
+
+    vec->_v(0) = _this->_v(0) * scalar;
+    vec->_v(1) = _this->_v(1) * scalar;
+    vec->_v(2) = _this->_v(2) * scalar;
+  }
+  return py_vec;
+}
+
 /**
  *
  */
@@ -349,5 +370,45 @@ __ceil__(PyObject *self) const {
   return py_vec;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH int Extension<FLOATNAME(LVecBase3)>::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+    PyErr_SetString(PyExc_BufferError,
+                    "Object is not writable.");
+    return -1;
+  }
+
+  static const char format[2] = {FLOATTOKEN, 0};
+  static const Py_ssize_t shape = FLOATNAME(LVecBase3)::num_components;
+
+  Py_INCREF(self);
+
+  view->buf = (void *)_this->get_data();
+  view->obj = self;
+  view->len = 3 * sizeof(FLOATTYPE);
+  view->readonly = 1;
+  view->itemsize = sizeof(FLOATTYPE);
+  view->format = nullptr;
+  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+    view->format = (char *)format;
+  }
+  view->ndim = 1;
+  view->shape = nullptr;
+  if ((flags & PyBUF_ND) == PyBUF_ND) {
+    view->shape = (Py_ssize_t *)&shape;
+  }
+  view->strides = nullptr;
+  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+    view->strides = &view->itemsize;
+  }
+  view->suboffsets = nullptr;
+  view->internal = nullptr;
+
+  return 0;
+}
+
 #undef PYNUMBER_FLOATTYPE
 #undef PY_AS_FLOATTYPE

+ 4 - 0
panda/src/linmath/lvecBase3_ext_src.h

@@ -23,6 +23,8 @@ public:
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
   INLINE_LINMATH std::string __repr__() const;
 
+  INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const;
+
   INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const;
   INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar);
 
@@ -32,6 +34,8 @@ public:
   INLINE_LINMATH PyObject *__round__(PyObject *self) const;
   INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
   INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+  INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
 };
 
 #include "lvecBase3_ext_src.I"

+ 4 - 0
panda/src/linmath/lvecBase3_src.h

@@ -152,6 +152,8 @@ PUBLISHED:
 
   INLINE_LINMATH void componentwise_mult(const FLOATNAME(LVecBase3) &other);
 
+  EXTENSION(INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const);
+
   EXTENSION(INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const);
   EXTENSION(INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar));
 
@@ -179,6 +181,8 @@ PUBLISHED:
   INLINE_LINMATH void write_datagram(Datagram &destination) const;
   INLINE_LINMATH void read_datagram(DatagramIterator &source);
 
+  EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
 public:
   // The underlying implementation is via the Eigen library, if available.
 

+ 62 - 0
panda/src/linmath/lvecBase4_ext_src.I

@@ -199,6 +199,28 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   return 0;
 }
 
+
+/**
+ *
+ */
+INLINE_LINMATH PyObject *Extension<FLOATNAME(LVecBase4)>::
+__rmul__(PyObject *self, FLOATTYPE scalar) const {
+#ifndef CPPPARSER
+  extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase4);
+#endif
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
+  if (py_vec != nullptr) {
+    FLOATNAME(LVecBase4) *vec = (FLOATNAME(LVecBase4) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase4));
+    nassertr(vec != nullptr, nullptr);
+
+    vec->_v(0) = _this->_v(0) * scalar;
+    vec->_v(1) = _this->_v(1) * scalar;
+    vec->_v(2) = _this->_v(2) * scalar;
+    vec->_v(3) = _this->_v(3) * scalar;
+  }
+  return py_vec;
+}
+
 /**
  *
  */
@@ -366,5 +388,45 @@ __ceil__(PyObject *self) const {
   return py_vec;
 }
 
+/**
+ *
+ */
+INLINE_LINMATH int Extension<FLOATNAME(LVecBase4)>::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+  if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+    PyErr_SetString(PyExc_BufferError,
+                    "Object is not writable.");
+    return -1;
+  }
+
+  static const char format[2] = {FLOATTOKEN, 0};
+  static const Py_ssize_t shape = FLOATNAME(LVecBase4)::num_components;
+
+  Py_INCREF(self);
+
+  view->buf = (void *)_this->get_data();
+  view->obj = self;
+  view->len = 4 * sizeof(FLOATTYPE);
+  view->readonly = 1;
+  view->itemsize = sizeof(FLOATTYPE);
+  view->format = nullptr;
+  if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+    view->format = (char *)format;
+  }
+  view->ndim = 1;
+  view->shape = nullptr;
+  if ((flags & PyBUF_ND) == PyBUF_ND) {
+    view->shape = (Py_ssize_t *)&shape;
+  }
+  view->strides = nullptr;
+  if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+    view->strides = &view->itemsize;
+  }
+  view->suboffsets = nullptr;
+  view->internal = nullptr;
+
+  return 0;
+}
+
 #undef PYNUMBER_FLOATTYPE
 #undef PY_AS_FLOATTYPE

+ 4 - 0
panda/src/linmath/lvecBase4_ext_src.h

@@ -23,6 +23,8 @@ public:
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
   INLINE_LINMATH std::string __repr__() const;
 
+  INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const;
+
   INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const;
   INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar);
 
@@ -32,6 +34,8 @@ public:
   INLINE_LINMATH PyObject *__round__(PyObject *self) const;
   INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
   INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+  INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
 };
 
 #include "lvecBase4_ext_src.I"

+ 4 - 0
panda/src/linmath/lvecBase4_src.h

@@ -160,6 +160,8 @@ PUBLISHED:
 
   INLINE_LINMATH void componentwise_mult(const FLOATNAME(LVecBase4) &other);
 
+  EXTENSION(INLINE_LINMATH PyObject *__rmul__(PyObject *self, FLOATTYPE scalar) const);
+
   EXTENSION(INLINE_LINMATH PyObject *__floordiv__(PyObject *self, FLOATTYPE scalar) const);
   EXTENSION(INLINE_LINMATH PyObject *__ifloordiv__(PyObject *self, FLOATTYPE scalar));
 
@@ -185,6 +187,8 @@ PUBLISHED:
   INLINE_LINMATH void write_datagram(Datagram &destination) const;
   INLINE_LINMATH void read_datagram(DatagramIterator &source);
 
+  EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
 public:
   // The underlying implementation is via the Eigen library, if available.
 

+ 8 - 0
panda/src/linmath/lvector2_ext_src.I

@@ -79,3 +79,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase2.
   return invoke_extension<FLOATNAME(LVecBase2)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LVector2) Extension<FLOATNAME(LVector2)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LVector2)> : public ExtensionBase<FLOATNAME(LVector2)>
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LVector2) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lvector2_src.h

@@ -40,6 +40,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LVector2) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LVector2) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LVector2) __rmul__(FLOATTYPE scalar) const);
+
 #ifndef FLOATTYPE_IS_INT
   INLINE_LINMATH FLOATNAME(LVector2) normalized() const;
   INLINE_LINMATH FLOATNAME(LVector2) project(const FLOATNAME(LVecBase2) &onto) const;

+ 8 - 0
panda/src/linmath/lvector3_ext_src.I

@@ -80,3 +80,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase3.
   return invoke_extension<FLOATNAME(LVecBase3)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LVector3) Extension<FLOATNAME(LVector3)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LVector3)> : public ExtensionBase<FLOATNAME(LVector3)>
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LVector3) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lvector3_src.h

@@ -71,6 +71,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LVector3) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LVector3) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LVector3) __rmul__(FLOATTYPE scalar) const);
+
   // Some special named constructors for LVector3.
 
   INLINE_LINMATH static FLOATNAME(LVector3) up(CoordinateSystem cs = CS_default);

+ 8 - 0
panda/src/linmath/lvector4_ext_src.I

@@ -85,3 +85,11 @@ __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign) {
   // Upcall to LVecBase4.
   return invoke_extension<FLOATNAME(LVecBase4)>(_this).__setattr__(self, attr_name, assign);
 }
+
+/**
+ *
+ */
+INLINE_LINMATH FLOATNAME(LVector4) Extension<FLOATNAME(LVector4)>::
+__rmul__(FLOATTYPE scalar) const {
+  return *_this * scalar;
+}

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

@@ -20,6 +20,9 @@ class Extension<FLOATNAME(LVector4)> : public ExtensionBase<FLOATNAME(LVector4)>
 public:
   INLINE_LINMATH PyObject *__getattr__(PyObject *self, const std::string &attr_name) const;
   INLINE_LINMATH int __setattr__(PyObject *self, const std::string &attr_name, PyObject *assign);
+
+  INLINE_LINMATH FLOATNAME(LVector4) __rmul__(FLOATTYPE scalar) const;
+
   INLINE_LINMATH std::string __repr__() const;
 };
 

+ 2 - 0
panda/src/linmath/lvector4_src.h

@@ -48,6 +48,8 @@ PUBLISHED:
   INLINE_LINMATH FLOATNAME(LVector4) operator * (FLOATTYPE scalar) const;
   INLINE_LINMATH FLOATNAME(LVector4) operator / (FLOATTYPE scalar) const;
 
+  EXTENSION(INLINE_LINMATH FLOATNAME(LVector4) __rmul__(FLOATTYPE scalar) const);
+
 #ifndef FLOATTYPE_IS_INT
   INLINE_LINMATH FLOATNAME(LVector4) normalized() const;
   INLINE_LINMATH FLOATNAME(LVector4) project(const FLOATNAME(LVecBase4) &onto) const;

+ 2 - 0
panda/src/pgraph/CMakeLists.txt

@@ -29,6 +29,7 @@ set(P3PGRAPH_HEADERS
   cullTraverserData.I cullTraverserData.h
   cullableObject.I cullableObject.h
   decalEffect.I decalEffect.h
+  depthBiasAttrib.I depthBiasAttrib.h
   depthOffsetAttrib.I depthOffsetAttrib.h
   depthTestAttrib.I depthTestAttrib.h
   depthWriteAttrib.I depthWriteAttrib.h
@@ -131,6 +132,7 @@ set(P3PGRAPH_SOURCES
   cullTraverserData.cxx
   cullableObject.cxx
   decalEffect.cxx
+  depthBiasAttrib.cxx
   depthOffsetAttrib.cxx
   depthTestAttrib.cxx
   depthWriteAttrib.cxx

+ 3 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -33,6 +33,7 @@
 #include "cullTraverser.h"
 #include "cullableObject.h"
 #include "decalEffect.h"
+#include "depthBiasAttrib.h"
 #include "depthOffsetAttrib.h"
 #include "depthTestAttrib.h"
 #include "depthWriteAttrib.h"
@@ -416,6 +417,7 @@ init_libpgraph() {
   CullTraverser::init_type();
   CullableObject::init_type();
   DecalEffect::init_type();
+  DepthBiasAttrib::init_type();
   DepthOffsetAttrib::init_type();
   DepthTestAttrib::init_type();
   DepthWriteAttrib::init_type();
@@ -489,6 +491,7 @@ init_libpgraph() {
   CullBinAttrib::register_with_read_factory();
   CullFaceAttrib::register_with_read_factory();
   DecalEffect::register_with_read_factory();
+  DepthBiasAttrib::register_with_read_factory();
   DepthOffsetAttrib::register_with_read_factory();
   DepthTestAttrib::register_with_read_factory();
   DepthWriteAttrib::register_with_read_factory();

+ 49 - 0
panda/src/pgraph/depthBiasAttrib.I

@@ -0,0 +1,49 @@
+/**
+ * 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 depthBiasAttrib.I
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+/**
+ * Use DepthBiasAttrib::make() to construct a new DepthBiasAttrib object.
+ */
+INLINE DepthBiasAttrib::
+DepthBiasAttrib(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                PN_stdfloat clamp) :
+  _slope_factor(slope_factor),
+  _constant_factor(constant_factor),
+  _clamp(clamp)
+{
+}
+
+/**
+ * Returns the slope factor.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_slope_factor() const {
+  return _slope_factor;
+}
+
+/**
+ * Returns the constant factor.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_constant_factor() const {
+  return _constant_factor;
+}
+
+/**
+ * Returns the maximum (or minimum, if negative) value of the bias.  If zero,
+ * no clamping is performed.
+ */
+INLINE PN_stdfloat DepthBiasAttrib::
+get_clamp() const {
+  return _clamp;
+}

+ 180 - 0
panda/src/pgraph/depthBiasAttrib.cxx

@@ -0,0 +1,180 @@
+/**
+ * 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 depthBiasAttrib.cxx
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+#include "depthBiasAttrib.h"
+#include "graphicsStateGuardianBase.h"
+#include "dcast.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+
+TypeHandle DepthBiasAttrib::_type_handle;
+int DepthBiasAttrib::_attrib_slot;
+
+/**
+ * Constructs a new DepthBiasAttrib object that indicates the slope factor,
+ * constant factor, and an optional clamping value.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+make(PN_stdfloat slope_factor, PN_stdfloat constant_factor, PN_stdfloat clamp) {
+  DepthBiasAttrib *attrib = new DepthBiasAttrib(slope_factor, constant_factor, clamp);
+  return return_new(attrib);
+}
+
+/**
+ * Returns a RenderAttrib that corresponds to whatever the standard default
+ * properties for render attributes of this type ought to be.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+make_default() {
+  return return_new(new DepthBiasAttrib(0, 0, 0));
+}
+
+/**
+ *
+ */
+void DepthBiasAttrib::
+output(std::ostream &out) const {
+  out << get_type() << ":(" << get_slope_factor() << ", " << get_constant_factor()
+      << ", " << get_clamp() << ")";
+}
+
+/**
+ * Intended to be overridden by derived DepthBiasAttrib types to return a
+ * unique number indicating whether this DepthBiasAttrib is equivalent to
+ * the other one.
+ *
+ * This should return 0 if the two DepthBiasAttrib objects are equivalent, a
+ * number less than zero if this one should be sorted before the other one,
+ * and a number greater than zero otherwise.
+ *
+ * This will only be called with two DepthBiasAttrib objects whose
+ * get_type() functions return the same.
+ */
+int DepthBiasAttrib::
+compare_to_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ta = (const DepthBiasAttrib *)other;
+
+  if (_slope_factor != ta->_slope_factor) {
+    return _slope_factor < ta->_slope_factor ? -1 : 1;
+  }
+  if (_constant_factor != ta->_constant_factor) {
+    return _constant_factor < ta->_constant_factor ? -1 : 1;
+  }
+  if (_clamp != ta->_clamp) {
+    return _clamp < ta->_clamp ? -1 : 1;
+  }
+  return 0;
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to return a unique
+ * hash for these particular properties.  RenderAttribs that compare the same
+ * with compare_to_impl(), above, should return the same hash; RenderAttribs
+ * that compare differently should return a different hash.
+ */
+size_t DepthBiasAttrib::
+get_hash_impl() const {
+  size_t hash = 0;
+  hash = float_hash().add_hash(hash, _slope_factor);
+  hash = float_hash().add_hash(hash, _constant_factor);
+  hash = float_hash().add_hash(hash, _clamp);
+  return hash;
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to specify how two
+ * consecutive RenderAttrib objects of the same type interact.
+ *
+ * This should return the result of applying the other RenderAttrib to a node
+ * in the scene graph below this RenderAttrib, which was already applied.  In
+ * most cases, the result is the same as the other RenderAttrib (that is, a
+ * subsequent RenderAttrib completely replaces the preceding one).  On the
+ * other hand, some kinds of RenderAttrib (for instance, ColorTransformAttrib)
+ * might combine in meaningful ways.
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+compose_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ba = (const DepthBiasAttrib *)other;
+
+  return return_new(new DepthBiasAttrib(ba->_slope_factor + _slope_factor,
+                                        ba->_constant_factor + _constant_factor,
+                                        ba->_clamp));
+}
+
+/**
+ * Intended to be overridden by derived RenderAttrib types to specify how two
+ * consecutive RenderAttrib objects of the same type interact.
+ *
+ * See invert_compose() and compose_impl().
+ */
+CPT(RenderAttrib) DepthBiasAttrib::
+invert_compose_impl(const RenderAttrib *other) const {
+  const DepthBiasAttrib *ba = (const DepthBiasAttrib *)other;
+
+  return return_new(new DepthBiasAttrib(ba->_slope_factor - _slope_factor,
+                                        ba->_constant_factor - _constant_factor,
+                                        ba->_clamp));
+}
+
+/**
+ * Tells the BamReader how to create objects of type DepthBiasAttrib.
+ */
+void DepthBiasAttrib::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void DepthBiasAttrib::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  RenderAttrib::write_datagram(manager, dg);
+
+  dg.add_stdfloat(_slope_factor);
+  dg.add_stdfloat(_constant_factor);
+  dg.add_stdfloat(_clamp);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type DepthBiasAttrib is encountered in the Bam file.  It should create
+ * the DepthBiasAttrib and extract its information from the file.
+ */
+TypedWritable *DepthBiasAttrib::
+make_from_bam(const FactoryParams &params) {
+  DepthBiasAttrib *attrib = new DepthBiasAttrib(0, 0, 0);
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  attrib->fillin(scan, manager);
+
+  return attrib;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new DepthBiasAttrib.
+ */
+void DepthBiasAttrib::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  RenderAttrib::fillin(scan, manager);
+
+  _slope_factor = scan.get_stdfloat();
+  _constant_factor = scan.get_stdfloat();
+  _clamp = scan.get_stdfloat();
+}

+ 115 - 0
panda/src/pgraph/depthBiasAttrib.h

@@ -0,0 +1,115 @@
+/**
+ * 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 depthBiasAttrib.h
+ * @author rdb
+ * @date 2021-08-24
+ */
+
+#ifndef DEPTHBIASATTRIB_H
+#define DEPTHBIASATTRIB_H
+
+#include "pandabase.h"
+
+#include "renderAttrib.h"
+#include "luse.h"
+
+class FactoryParams;
+
+/**
+ * This is a special kind of attribute that instructs the graphics driver to
+ * apply an offset or bias to the generated depth values for rendered
+ * polygons, before they are written to the depth buffer.
+ *
+ * This class replaces the old DepthOffsetAttrib, which had a more limited
+ * parameterization.  The differences are:
+ * - The sign of the factor parameter was inverted.
+ * - The slope and constant factors are specified separately.
+ * - The factors are specified as floating-point instead of integer.
+ * - There is a new clamp parameter.
+ *
+ * Nested DepthBiasAttrib values accumulate; that is, a DepthBiasAttrib
+ * with a value of 1 beneath another DepthBiasAttrib with a value of 2
+ * presents a net offset of 3.  (A DepthBiasAttrib will not, however,
+ * combine with any other DepthBiasAttribs with a lower override parameter.)
+ */
+class EXPCL_PANDA_PGRAPH DepthBiasAttrib : public RenderAttrib {
+private:
+  INLINE DepthBiasAttrib(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                         PN_stdfloat clamp = 0);
+
+PUBLISHED:
+  static CPT(RenderAttrib) make(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                                PN_stdfloat clamp = 0);
+  static CPT(RenderAttrib) make_default();
+
+public:
+  INLINE PN_stdfloat get_slope_factor() const;
+  INLINE PN_stdfloat get_constant_factor() const;
+  INLINE PN_stdfloat get_clamp() const;
+
+PUBLISHED:
+  MAKE_PROPERTY(slope_factor, get_slope_factor);
+  MAKE_PROPERTY(constant_factor, get_constant_factor);
+  MAKE_PROPERTY(clamp, get_clamp);
+
+public:
+  virtual void output(std::ostream &out) const;
+
+protected:
+  virtual int compare_to_impl(const RenderAttrib *other) const;
+  virtual size_t get_hash_impl() const;
+  virtual CPT(RenderAttrib) compose_impl(const RenderAttrib *other) const;
+  virtual CPT(RenderAttrib) invert_compose_impl(const RenderAttrib *other) const;
+
+private:
+  PN_stdfloat _slope_factor;
+  PN_stdfloat _constant_factor;
+  PN_stdfloat _clamp;
+
+PUBLISHED:
+  static int get_class_slot() {
+    return _attrib_slot;
+  }
+  virtual int get_slot() const {
+    return get_class_slot();
+  }
+  MAKE_PROPERTY(class_slot, get_class_slot);
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    RenderAttrib::init_type();
+    register_type(_type_handle, "DepthBiasAttrib",
+                  RenderAttrib::get_class_type());
+    _attrib_slot = register_slot(_type_handle, 100,
+                                 new DepthBiasAttrib(0, 0, 0));
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+  static int _attrib_slot;
+};
+
+#include "depthBiasAttrib.I"
+
+#endif

+ 2 - 0
panda/src/pgraph/depthOffsetAttrib.h

@@ -46,6 +46,8 @@ class FactoryParams;
  * Also, and only tangentially related, the DepthOffsetAttrib can be used to
  * constrain the Z output value to a subset of the usual [0, 1] range (or
  * reversing its direction) by specifying a new min_value and max_value.
+ *
+ * @deprecated See DepthBiasAttrib and DisplayRegion::set_depth_range() instead.
  */
 class EXPCL_PANDA_PGRAPH DepthOffsetAttrib : public RenderAttrib {
 private:

+ 42 - 1
panda/src/pgraph/nodePath.cxx

@@ -34,9 +34,10 @@
 #include "renderModeAttrib.h"
 #include "cullFaceAttrib.h"
 #include "alphaTestAttrib.h"
+#include "depthBiasAttrib.h"
+#include "depthOffsetAttrib.h"
 #include "depthTestAttrib.h"
 #include "depthWriteAttrib.h"
-#include "depthOffsetAttrib.h"
 #include "shaderAttrib.h"
 #include "billboardEffect.h"
 #include "compassEffect.h"
@@ -309,6 +310,8 @@ get_sort(Thread *current_thread) const {
  * Searches for a node below the referenced node that matches the indicated
  * string.  Returns the shortest match found, if any, or an empty NodePath if
  * no match can be found.
+ *
+ * The referenced node itself is not considered in the search.
  */
 NodePath NodePath::
 find(const string &path) const {
@@ -349,6 +352,8 @@ find_path_to(PandaNode *node) const {
 /**
  * Returns the complete set of all NodePaths that begin with this NodePath and
  * can be extended by path.  The shortest paths will be listed first.
+ *
+ * The referenced node itself is not considered in the search.
  */
 NodePathCollection NodePath::
 find_all_matches(const string &path) const {
@@ -4684,6 +4689,8 @@ get_depth_write() const {
  * bias is always an integer number, and each integer increment represents the
  * smallest possible increment in Z that is sufficient to completely resolve
  * two coplanar polygons.  Positive numbers are closer towards the camera.
+ *
+ * @deprecated See set_depth_bias() instead, which provides more controls.
  */
 void NodePath::
 set_depth_offset(int bias, int priority) {
@@ -4730,6 +4737,40 @@ get_depth_offset() const {
   return 0;
 }
 
+/**
+ * This instructs the graphics driver to apply an offset or bias to the
+ * generated depth values for rendered polygons, before they are written to
+ * the depth buffer.  This can be used to shift polygons forward slightly, to
+ * resolve depth conflicts, or self-shadowing artifacts on thin objects.
+ * Positive numbers are further away from the camera.
+ */
+void NodePath::
+set_depth_bias(PN_stdfloat slope_factor, PN_stdfloat constant_factor, PN_stdfloat clamp, int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(DepthBiasAttrib::make(slope_factor, constant_factor, clamp), priority);
+}
+
+/**
+ * Completely removes any depth-bias adjustment that may have been set on
+ * this node via set_depth_bias().
+ */
+void NodePath::
+clear_depth_bias() {
+  nassertv_always(!is_empty());
+  node()->clear_attrib(DepthBiasAttrib::get_class_slot());
+}
+
+/**
+ * Returns true if a depth-bias adjustment has been explicitly set on this
+ * particular node via set_depth_bias().  If this returns true, then
+ * get_depth_bias() may be called to determine which has been set.
+ */
+bool NodePath::
+has_depth_bias() const {
+  nassertr_always(!is_empty(), false);
+  return node()->has_attrib(DepthBiasAttrib::get_class_slot());
+}
+
 /**
  * Performs a billboard-type rotate to the indicated camera node, one time
  * only, and leaves the object rotated.  This is similar in principle to

+ 5 - 0
panda/src/pgraph/nodePath.h

@@ -811,6 +811,11 @@ PUBLISHED:
   bool has_depth_offset() const;
   int get_depth_offset() const;
 
+  void set_depth_bias(PN_stdfloat slope_factor, PN_stdfloat constant_factor,
+                      PN_stdfloat clamp = 0.0, int priority = 0);
+  void clear_depth_bias();
+  bool has_depth_bias() const;
+
   void do_billboard_axis(const NodePath &camera, PN_stdfloat offset);
   void do_billboard_point_eye(const NodePath &camera, PN_stdfloat offset);
   void do_billboard_point_world(const NodePath &camera, PN_stdfloat offset);

+ 1 - 0
panda/src/pgraph/p3pgraph_composite2.cxx

@@ -9,6 +9,7 @@
 #include "cullTraverserData.cxx"
 #include "cullableObject.cxx"
 #include "decalEffect.cxx"
+#include "depthBiasAttrib.cxx"
 #include "depthOffsetAttrib.cxx"
 #include "depthTestAttrib.cxx"
 #include "depthWriteAttrib.cxx"

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است