Browse Source

Merge branch 'release/1.10.x' into master

rdb 5 years ago
parent
commit
66ac3be604
38 changed files with 494 additions and 127 deletions
  1. 21 21
      contrib/src/rplight/rpLight.I
  2. 5 1
      direct/src/actor/Actor.py
  3. 3 0
      direct/src/actor/__init__.py
  4. 1 0
      direct/src/directscripts/Doxyfile.cxx
  5. 2 1
      direct/src/dist/commands.py
  6. 3 0
      direct/src/distributed/__init__.py
  7. 119 3
      direct/src/filter/CommonFilters.py
  8. 7 1
      direct/src/filter/FilterManager.py
  9. 3 0
      direct/src/filter/__init__.py
  10. 3 0
      direct/src/showbase/__init__.py
  11. 2 1
      direct/src/stdpy/__init__.py
  12. 14 8
      direct/src/tkpanels/Inspector.py
  13. 2 0
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  14. 11 3
      dtool/src/interrogatedb/py_panda.cxx
  15. 6 6
      dtool/src/prc/configPageManager.I
  16. 2 2
      dtool/src/prc/configVariableManager.cxx
  17. 3 3
      dtool/src/prc/streamReader.h
  18. 60 9
      dtool/src/prc/streamReader_ext.cxx
  19. 3 3
      dtool/src/prc/streamReader_ext.h
  20. 6 3
      makepanda/makepackage.py
  21. 20 12
      makepanda/makepandacore.py
  22. 5 7
      panda/src/audio/audioManager.h
  23. 3 3
      panda/src/audio/audioSound.cxx
  24. 1 1
      panda/src/audio/audioSound.h
  25. 13 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  26. 1 1
      panda/src/downloader/httpClient.cxx
  27. 10 0
      panda/src/egg/eggTexture.cxx
  28. 4 1
      panda/src/egg/eggTexture.h
  29. 9 0
      panda/src/egg2pg/eggLoader.cxx
  30. 6 0
      panda/src/egg2pg/eggSaver.cxx
  31. 17 6
      panda/src/egldisplay/eglGraphicsStateGuardian.cxx
  32. 18 1
      panda/src/egldisplay/eglGraphicsWindow.cxx
  33. 3 2
      panda/src/gles2gsg/gles2gsg.h
  34. 20 18
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  35. 1 1
      panda/src/linmath/lmatrix4_src.I
  36. 16 8
      panda/src/windisplay/winGraphicsWindow.cxx
  37. 69 1
      pandatool/src/deploy-stub/deploy-stub.c
  38. 2 0
      pandatool/src/palettizer/textureProperties.cxx

+ 21 - 21
contrib/src/rplight/rpLight.I

@@ -148,7 +148,7 @@ inline void RPLight::assign_slot(int slot) {
  *   changed. This will cause all shadow sources to be updated, emitting a
  *   changed. This will cause all shadow sources to be updated, emitting a
  *   shadow update. Be careful when calling this method if you don't want all
  *   shadow update. Be careful when calling this method if you don't want all
  *   sources to get updated. If you only have to invalidate a single shadow source,
  *   sources to get updated. If you only have to invalidate a single shadow source,
- *   use get_shadow_source(n)->set_needs_update(true).
+ *   use `get_shadow_source(n)->set_needs_update(true)`.
  */
  */
 inline void RPLight::invalidate_shadows() {
 inline void RPLight::invalidate_shadows() {
   for (size_t i = 0; i < _shadow_sources.size(); ++i) {
   for (size_t i = 0; i < _shadow_sources.size(); ++i) {
@@ -296,14 +296,14 @@ inline bool RPLight::get_casts_shadows() const {
 }
 }
 
 
 /**
 /**
- * @brief Sets the lights shadow map resolution
- * @details This sets the lights shadow map resolution. This has no effect
+ * @brief Sets the light's shadow map resolution
+ * @details This sets the light's shadow map resolution. This has no effect
  *   when the light is not told to cast shadows (Use RPLight::set_casts_shadows).
  *   when the light is not told to cast shadows (Use RPLight::set_casts_shadows).
  *
  *
- *   When calling this on a light with multiple shadow sources (e.g. PointLight),
- *   this controls the resolution of each source. If the light has 6 shadow sources,
- *   and you use a resolution of 512x512, the lights shadow map will occur a
- *   space of 6 * 512x512 maps in the shadow atlas.
+ *   When calling this on a light with multiple shadow sources (e.g.
+ *   RPPointLight), this controls the resolution of each source. If the light
+ *   has 6 shadow sources, and you use a resolution of 512x512, the light's
+ *   shadow map will occupy a space of 6 * 512x512 maps in the shadow atlas.
  *
  *
  * @param resolution Resolution of the shadow map in pixels
  * @param resolution Resolution of the shadow map in pixels
  */
  */
@@ -326,14 +326,14 @@ inline size_t RPLight::get_shadow_map_resolution() const {
 }
 }
 
 
 /**
 /**
- * @brief Sets the ies profile
+ * @brief Sets the IES profile
  * @details This sets the ies profile of the light. The parameter should be a
  * @details This sets the ies profile of the light. The parameter should be a
  *   handle previously returned by RenderPipeline.load_ies_profile. Using a
  *   handle previously returned by RenderPipeline.load_ies_profile. Using a
  *   value of -1 indicates no ies profile.
  *   value of -1 indicates no ies profile.
  *
  *
- *   Notice that for ies profiles which cover a whole range, you should use
- *   PointLights, whereas for ies profiles which only cover the lower hemisphere
- *   you should use SpotLights for the best performance.
+ *   Notice that for IES profiles which cover a whole range, you should use an
+ *   RPPointLight, whereas for ies profiles which only cover the lower
+ *   hemisphere you should use an RPSpotLight for the best performance.
  *
  *
  * @param profile IES Profile handle
  * @param profile IES Profile handle
  */
  */
@@ -343,8 +343,8 @@ inline void RPLight::set_ies_profile(int profile) {
 }
 }
 
 
 /**
 /**
- * @brief Returns the lights ies profile
- * @details This returns the ies profile of a light, previously set with
+ * @brief Returns the light's IES profile
+ * @details This returns the IES profile of a light, previously set with
  *   RPLight::set_ies_profile. In case no ies profile was set, returns -1.
  *   RPLight::set_ies_profile. In case no ies profile was set, returns -1.
  *
  *
  * @return IES Profile handle
  * @return IES Profile handle
@@ -354,20 +354,20 @@ inline int RPLight::get_ies_profile() const {
 }
 }
 
 
 /**
 /**
- * @brief Returns whether the light has an ies profile assigned
- * @details This returns whether the light has an ies profile assigned,
+ * @brief Returns whether the light has an IES profile assigned
+ * @details This returns whether the light has an IES profile assigned,
  *   previously done with RPLight::set_ies_profile.
  *   previously done with RPLight::set_ies_profile.
  *
  *
- * @return true if the light has an ies profile assigned, false otherwise
+ * @return true if the light has an IES profile assigned, false otherwise
  */
  */
 inline bool RPLight::has_ies_profile() const {
 inline bool RPLight::has_ies_profile() const {
   return _ies_profile >= 0;
   return _ies_profile >= 0;
 }
 }
 
 
 /**
 /**
- * @brief Clears the ies profile
- * @details This clears the ies profile of the light, telling it to no longer
- *   use an ies profile, and instead use the default attenuation.
+ * @brief Clears the IES profile
+ * @details This clears the IES profile of the light, telling it to no longer
+ *   use an IES profile, and instead use the default attenuation.
  */
  */
 inline void RPLight::clear_ies_profile() {
 inline void RPLight::clear_ies_profile() {
   set_ies_profile(-1);
   set_ies_profile(-1);
@@ -377,7 +377,7 @@ inline void RPLight::clear_ies_profile() {
  * @brief Sets the near plane of the light
  * @brief Sets the near plane of the light
  * @details This sets the near plane of all shadow sources of the light. It has
  * @details This sets the near plane of all shadow sources of the light. It has
  *   no effects if the light does not cast shadows. This prevents artifacts from
  *   no effects if the light does not cast shadows. This prevents artifacts from
- *   objects near to the light. It behaves like Lens::set_near_plane.
+ *   objects near to the light. It behaves like Lens::set_near().
  *
  *
  *   It can also help increasing shadow map precision, low near planes will
  *   It can also help increasing shadow map precision, low near planes will
  *   cause the precision to suffer. Try setting the near plane as big as possible.
  *   cause the precision to suffer. Try setting the near plane as big as possible.
@@ -394,7 +394,7 @@ inline void RPLight::set_near_plane(float near_plane) {
 
 
 /**
 /**
  * @brief Returns the near plane of the light
  * @brief Returns the near plane of the light
- * @details This returns the lights near plane, previously set with
+ * @details This returns the light's near plane, previously set with
  *   RPLight::set_near_plane. If the light does not cast shadows, this value
  *   RPLight::set_near_plane. If the light does not cast shadows, this value
  *   is meaningless.
  *   is meaningless.
  *
  *

+ 5 - 1
direct/src/actor/Actor.py

@@ -1,4 +1,8 @@
-"""Actor module: contains the Actor class"""
+"""Actor module: contains the Actor class.
+
+See the :ref:`models-and-actors` page in the Programming Guide to learn
+more about loading models and animated actors.
+"""
 
 
 __all__ = ['Actor']
 __all__ = ['Actor']
 
 

+ 3 - 0
direct/src/actor/__init__.py

@@ -4,4 +4,7 @@ distributed variant thereof.  Actor is a high-level interface around
 the lower-level :class:`panda3d.core.Character` implementation.
 the lower-level :class:`panda3d.core.Character` implementation.
 It loads and controls an animated character and manages the animations
 It loads and controls an animated character and manages the animations
 playing on it.
 playing on it.
+
+See the :ref:`models-and-actors` page in the Programming Guide to learn
+more about loading models and animated actors.
 """
 """

+ 1 - 0
direct/src/directscripts/Doxyfile.cxx

@@ -789,6 +789,7 @@ RECURSIVE              = YES
 EXCLUDE                = dtool/src/parser-inc \
 EXCLUDE                = dtool/src/parser-inc \
                          dtool/src/cppparser \
                          dtool/src/cppparser \
                          dtool/src/interrogate \
                          dtool/src/interrogate \
+                         direct/src/directscripts \
                          direct/src/plugin \
                          direct/src/plugin \
                          direct/src/plugin_standalone \
                          direct/src/plugin_standalone \
                          direct/src/plugin_npapi \
                          direct/src/plugin_npapi \

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

@@ -369,12 +369,13 @@ class build_apps(setuptools.Command):
                     os.remove(os.path.join(whldir, whl))
                     os.remove(os.path.join(whldir, whl))
 
 
         pip_args = [
         pip_args = [
+            '--disable-pip-version-check',
             'download',
             'download',
             '-d', whldir,
             '-d', whldir,
             '-r', self.requirements_path,
             '-r', self.requirements_path,
             '--only-binary', ':all:',
             '--only-binary', ':all:',
             '--platform', platform,
             '--platform', platform,
-            '--abi', abi_tag
+            '--abi', abi_tag,
         ]
         ]
 
 
         if self.use_optimized_wheels:
         if self.use_optimized_wheels:

+ 3 - 0
direct/src/distributed/__init__.py

@@ -2,4 +2,7 @@
 This package contains an implementation of the Distributed Networking
 This package contains an implementation of the Distributed Networking
 API, a high-level networking system that automatically propagates
 API, a high-level networking system that automatically propagates
 changes made on distributed objects to interested clients.
 changes made on distributed objects to interested clients.
+
+See the :ref:`distributed-networking` section of the Programming Guide to
+learn more about the distributed networking system.
 """
 """

+ 119 - 3
direct/src/filter/CommonFilters.py

@@ -1,7 +1,8 @@
 """
 """
 
 
 Class CommonFilters implements certain common image
 Class CommonFilters implements certain common image
-postprocessing filters.
+postprocessing filters.  See the :ref:`common-image-filters` page for
+more information about how to use these filters.
 
 
 It is not ideal that these filters are all included in a single
 It is not ideal that these filters are all included in a single
 monolithic module.  Unfortunately, when you want to apply two filters
 monolithic module.  Unfortunately, when you want to apply two filters
@@ -27,6 +28,7 @@ from panda3d.core import LVecBase4, LPoint2
 from panda3d.core import Filename
 from panda3d.core import Filename
 from panda3d.core import AuxBitplaneAttrib
 from panda3d.core import AuxBitplaneAttrib
 from panda3d.core import Texture, Shader, ATSNone
 from panda3d.core import Texture, Shader, ATSNone
+from panda3d.core import FrameBufferProperties
 import os
 import os
 
 
 CARTOON_BODY="""
 CARTOON_BODY="""
@@ -177,7 +179,15 @@ class CommonFilters:
                 self.textures[tex].setWrapU(Texture.WMClamp)
                 self.textures[tex].setWrapU(Texture.WMClamp)
                 self.textures[tex].setWrapV(Texture.WMClamp)
                 self.textures[tex].setWrapV(Texture.WMClamp)
 
 
-            self.finalQuad = self.manager.renderSceneInto(textures = self.textures, auxbits=auxbits)
+            fbprops = None
+            clamping = None
+            if "HighDynamicRange" in configuration:
+                fbprops = FrameBufferProperties()
+                fbprops.setFloatColor(True)
+                fbprops.setSrgbColor(False)
+                clamping = False
+
+            self.finalQuad = self.manager.renderSceneInto(textures = self.textures, auxbits=auxbits, fbprops=fbprops, clamping=clamping)
             if (self.finalQuad == None):
             if (self.finalQuad == None):
                 self.cleanup()
                 self.cleanup()
                 return False
                 return False
@@ -255,6 +265,17 @@ class CommonFilters:
             texcoordSets = list(enumerate(texcoordPadding.keys()))
             texcoordSets = list(enumerate(texcoordPadding.keys()))
 
 
             text = "//Cg\n"
             text = "//Cg\n"
+            if "HighDynamicRange" in configuration:
+                text += "static const float3x3 aces_input_mat = {\n"
+                text += "  {0.59719, 0.35458, 0.04823},\n"
+                text += "  {0.07600, 0.90834, 0.01566},\n"
+                text += "  {0.02840, 0.13383, 0.83777},\n"
+                text += "};\n"
+                text += "static const float3x3 aces_output_mat = {\n"
+                text += "  { 1.60475, -0.53108, -0.07367},\n"
+                text += "  {-0.10208,  1.10813, -0.00605},\n"
+                text += "  {-0.00327, -0.07276,  1.07602},\n"
+                text += "};\n"
             text += "void vshader(float4 vtx_position : POSITION,\n"
             text += "void vshader(float4 vtx_position : POSITION,\n"
             text += "  out float4 l_position : POSITION,\n"
             text += "  out float4 l_position : POSITION,\n"
 
 
@@ -301,6 +322,10 @@ class CommonFilters:
             if ("VolumetricLighting" in configuration):
             if ("VolumetricLighting" in configuration):
                 text += "  uniform float4 k_casterpos,\n"
                 text += "  uniform float4 k_casterpos,\n"
                 text += "  uniform float4 k_vlparams,\n"
                 text += "  uniform float4 k_vlparams,\n"
+
+            if ("ExposureAdjust" in configuration):
+                text += "  uniform float k_exposure,\n"
+
             text += "  out float4 o_color : COLOR)\n"
             text += "  out float4 o_color : COLOR)\n"
             text += "{\n"
             text += "{\n"
             text += "  o_color = tex2D(k_txcolor, %s);\n" % (texcoords["color"])
             text += "  o_color = tex2D(k_txcolor, %s);\n" % (texcoords["color"])
@@ -332,6 +357,14 @@ class CommonFilters:
                 text += "  }\n"
                 text += "  }\n"
                 text += "  o_color += float4(vlcolor * k_vlparams.z, 1);\n"
                 text += "  o_color += float4(vlcolor * k_vlparams.z, 1);\n"
 
 
+            if ("ExposureAdjust" in configuration):
+                text += "  o_color.rgb *= k_exposure;\n"
+
+            # With thanks to Stephen Hill!
+            if ("HighDynamicRange" in configuration):
+                text += "  float3 aces_color = mul(aces_input_mat, o_color.rgb);\n"
+                text += "  o_color.rgb = saturate(mul(aces_output_mat, (aces_color * (aces_color + 0.0245786f) - 0.000090537f) / (aces_color * (0.983729f * aces_color + 0.4329510f) + 0.238081f)));\n"
+
             if ("GammaAdjust" in configuration):
             if ("GammaAdjust" in configuration):
                 gamma = configuration["GammaAdjust"]
                 gamma = configuration["GammaAdjust"]
                 if gamma == 0.5:
                 if gamma == 0.5:
@@ -341,6 +374,11 @@ class CommonFilters:
                 elif gamma != 1.0:
                 elif gamma != 1.0:
                     text += "  o_color.rgb = pow(o_color.rgb, %ff);\n" % (gamma)
                     text += "  o_color.rgb = pow(o_color.rgb, %ff);\n" % (gamma)
 
 
+            if ("SrgbEncode" in configuration):
+                text += "  o_color.r = (o_color.r < 0.0031308) ? (o_color.r * 12.92) : (1.055 * pow(o_color.r, 0.41666) - 0.055);\n"
+                text += "  o_color.g = (o_color.g < 0.0031308) ? (o_color.g * 12.92) : (1.055 * pow(o_color.g, 0.41666) - 0.055);\n"
+                text += "  o_color.b = (o_color.b < 0.0031308) ? (o_color.b * 12.92) : (1.055 * pow(o_color.b, 0.41666) - 0.055);\n"
+
             if ("Inverted" in configuration):
             if ("Inverted" in configuration):
                 text += "  o_color = float4(1, 1, 1, 1) - o_color;\n"
                 text += "  o_color = float4(1, 1, 1, 1) - o_color;\n"
             text += "}\n"
             text += "}\n"
@@ -386,6 +424,11 @@ class CommonFilters:
                 self.ssao[0].setShaderInput("params1", config.numsamples, -float(config.amount) / config.numsamples, config.radius, 0)
                 self.ssao[0].setShaderInput("params1", config.numsamples, -float(config.amount) / config.numsamples, config.radius, 0)
                 self.ssao[0].setShaderInput("params2", config.strength, config.falloff, 0, 0)
                 self.ssao[0].setShaderInput("params2", config.strength, config.falloff, 0, 0)
 
 
+        if (changed == "ExposureAdjust") or fullrebuild:
+            if ("ExposureAdjust" in configuration):
+                stops = configuration["ExposureAdjust"]
+                self.finalQuad.setShaderInput("exposure", 2 ** stops)
+
         self.update()
         self.update()
         return True
         return True
 
 
@@ -547,6 +590,73 @@ class CommonFilters:
             return self.reconfigure((old_gamma != 1.0), "GammaAdjust")
             return self.reconfigure((old_gamma != 1.0), "GammaAdjust")
         return True
         return True
 
 
+    def setSrgbEncode(self, force=False):
+        """ Applies the inverse sRGB EOTF to the output, unless the window
+        already has an sRGB framebuffer, in which case this filter refuses to
+        apply, to prevent accidental double-application.
+
+        Set the force argument to True to force it to be applied in all cases.
+
+        .. versionadded:: 1.10.7
+        """
+        new_enable = force or not self.manager.win.getFbProperties().getSrgbColor()
+        old_enable = self.configuration.get("SrgbEncode", False)
+        if new_enable and not old_enable:
+            self.configuration["SrgbEncode"] = True
+            return self.reconfigure(True, "SrgbEncode")
+        elif not new_enable and old_enable:
+            del self.configuration["SrgbEncode"]
+        return new_enable
+
+    def delSrgbEncode(self):
+        """ Reverses the effects of setSrgbEncode. """
+        if ("SrgbEncode" in self.configuration):
+            old_enable = self.configuration["SrgbEncode"]
+            del self.configuration["SrgbEncode"]
+            return self.reconfigure(old_enable, "SrgbEncode")
+        return True
+
+    def setHighDynamicRange(self):
+        """ Enables HDR rendering by using a floating-point framebuffer,
+        disabling color clamping on the main scene, and applying a tone map
+        operator (ACES).
+
+        It may also be necessary to use setExposureAdjust to perform exposure
+        compensation on the scene, depending on the lighting intensity.
+
+        .. versionadded:: 1.10.7
+        """
+
+        fullrebuild = (("HighDynamicRange" in self.configuration) is False)
+        self.configuration["HighDynamicRange"] = 1
+        return self.reconfigure(fullrebuild, "HighDynamicRange")
+
+    def delHighDynamicRange(self):
+        if ("HighDynamicRange" in self.configuration):
+            del self.configuration["HighDynamicRange"]
+            return self.reconfigure(True, "HighDynamicRange")
+        return True
+
+    def setExposureAdjust(self, stops):
+        """ Sets a relative exposure adjustment to multiply with the result of
+        rendering the scene, in stops.  A value of 0 means no adjustment, a
+        positive value will result in a brighter image.  Useful in conjunction
+        with HDR, see setHighDynamicRange.
+
+        .. versionadded:: 1.10.7
+        """
+        old_stops = self.configuration.get("ExposureAdjust")
+        if old_stops != stops:
+            self.configuration["ExposureAdjust"] = stops
+            return self.reconfigure(old_stops is None, "ExposureAdjust")
+        return True
+
+    def delExposureAdjust(self):
+        if ("ExposureAdjust" in self.configuration):
+            del self.configuration["ExposureAdjust"]
+            return self.reconfigure(True, "ExposureAdjust")
+        return True
+
     #snake_case alias:
     #snake_case alias:
     del_cartoon_ink = delCartoonInk
     del_cartoon_ink = delCartoonInk
     set_half_pixel_shift = setHalfPixelShift
     set_half_pixel_shift = setHalfPixelShift
@@ -555,7 +665,6 @@ class CommonFilters:
     del_inverted = delInverted
     del_inverted = delInverted
     del_view_glow = delViewGlow
     del_view_glow = delViewGlow
     set_volumetric_lighting = setVolumetricLighting
     set_volumetric_lighting = setVolumetricLighting
-    del_gamma_adjust = delGammaAdjust
     set_bloom = setBloom
     set_bloom = setBloom
     set_view_glow = setViewGlow
     set_view_glow = setViewGlow
     set_ambient_occlusion = setAmbientOcclusion
     set_ambient_occlusion = setAmbientOcclusion
@@ -566,3 +675,10 @@ class CommonFilters:
     del_blur_sharpen = delBlurSharpen
     del_blur_sharpen = delBlurSharpen
     del_volumetric_lighting = delVolumetricLighting
     del_volumetric_lighting = delVolumetricLighting
     set_gamma_adjust = setGammaAdjust
     set_gamma_adjust = setGammaAdjust
+    del_gamma_adjust = delGammaAdjust
+    set_srgb_encode = setSrgbEncode
+    del_srgb_encode = delSrgbEncode
+    set_exposure_adjust = setExposureAdjust
+    del_exposure_adjust = delExposureAdjust
+    set_high_dynamic_range = setHighDynamicRange
+    del_high_dynamic_range = delHighDynamicRange

+ 7 - 1
direct/src/filter/FilterManager.py

@@ -3,6 +3,8 @@
 The FilterManager is a convenience class that helps with the creation
 The FilterManager is a convenience class that helps with the creation
 of render-to-texture buffers for image postprocessing applications.
 of render-to-texture buffers for image postprocessing applications.
 
 
+See :ref:`generalized-image-filters` for information on how to use this class.
+
 Still need to implement:
 Still need to implement:
 
 
 * Make sure sort-order of buffers is correct.
 * Make sure sort-order of buffers is correct.
@@ -22,6 +24,7 @@ from panda3d.core import WindowProperties, FrameBufferProperties
 from panda3d.core import Camera
 from panda3d.core import Camera
 from panda3d.core import OrthographicLens
 from panda3d.core import OrthographicLens
 from panda3d.core import AuxBitplaneAttrib
 from panda3d.core import AuxBitplaneAttrib
+from panda3d.core import LightRampAttrib
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 
 
@@ -124,7 +127,7 @@ class FilterManager(DirectObject):
 
 
         return winx,winy
         return winx,winy
 
 
-    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None):
+    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None, clamping=None):
 
 
         """ Causes the scene to be rendered into the supplied textures
         """ Causes the scene to be rendered into the supplied textures
         instead of into the original window.  Puts a fullscreen quad
         instead of into the original window.  Puts a fullscreen quad
@@ -207,6 +210,9 @@ class FilterManager(DirectObject):
         #cs.setShaderAuto()
         #cs.setShaderAuto()
         if (auxbits):
         if (auxbits):
             cs.setAttrib(AuxBitplaneAttrib.make(auxbits))
             cs.setAttrib(AuxBitplaneAttrib.make(auxbits))
+        if clamping is False:
+            # Disables clamping in the shader generator.
+            cs.setAttrib(LightRampAttrib.make_identity())
         self.camera.node().setInitialState(cs.getState())
         self.camera.node().setInitialState(cs.getState())
 
 
         quadcamnode = Camera("filter-quad-cam")
         quadcamnode = Camera("filter-quad-cam")

+ 3 - 0
direct/src/filter/__init__.py

@@ -8,4 +8,7 @@ texture values as desired.
 The :class:`.CommonFilters` class contains various filters that are
 The :class:`.CommonFilters` class contains various filters that are
 provided out of the box, whereas the :class:`.FilterManager` class
 provided out of the box, whereas the :class:`.FilterManager` class
 is a lower-level class that allows you to set up your own filters.
 is a lower-level class that allows you to set up your own filters.
+
+See the :ref:`render-to-texture-and-image-postprocessing` section of the
+Programming Guide to learn more about image postprocessing in Panda3D.
 """
 """

+ 3 - 0
direct/src/showbase/__init__.py

@@ -0,0 +1,3 @@
+""" This package contains `.ShowBase`, an application framework responsible
+for opening a graphical display, setting up input devices and creating
+the scene graph. """

+ 2 - 1
direct/src/stdpy/__init__.py

@@ -1,5 +1,6 @@
 """
 """
 This package contains various modules that provide a drop-in substitute
 This package contains various modules that provide a drop-in substitute
 for some of the built-in Python modules.  These substitutes make better
 for some of the built-in Python modules.  These substitutes make better
-use of Panda3D's virtual file system and threading system.
+use of Panda3D's :ref:`Virtual File System <virtual-file-system>` and
+:ref:`threading` system.
 """
 """

+ 14 - 8
direct/src/tkpanels/Inspector.py

@@ -1,8 +1,15 @@
 """Inspectors allow you to visually browse through the members of
 """Inspectors allow you to visually browse through the members of
-various python objects.  To open an inspector, import this module, and
-execute inspector.inspect(anObject) I start IDLE with this command
-line: idle.py -c "from inspector import inspect"
-so that I can just type: inspect(anObject) any time."""
+various Python objects.  To open an inspector, import this module, and
+execute ``inspector.inspect(anObject)``.
+
+I start IDLE with this command line::
+
+   idle.py -c "from inspector import inspect"
+
+so that I can just type: ``inspect(anObject)`` any time.
+
+See :ref:`inspection-utilities` for more information.
+"""
 
 
 
 
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
@@ -13,6 +20,9 @@ import Pmw
 ### public API
 ### public API
 
 
 def inspect(anObject):
 def inspect(anObject):
+    """Opens up a window for visually inspecting the details of a given Python
+    object.  See :ref:`inspection-utilities`.
+    """
     inspector = inspectorFor(anObject)
     inspector = inspectorFor(anObject)
     inspectorWindow = InspectorWindow(inspector)
     inspectorWindow = InspectorWindow(inspector)
     inspectorWindow.open()
     inspectorWindow.open()
@@ -437,7 +447,3 @@ class InspectorWindow:
                 label = item,
                 label = item,
                 command = lambda p = part, f = func: f(p))
                 command = lambda p = part, f = func: f(p))
         return popupMenu
         return popupMenu
-
-
-
-

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

@@ -6567,7 +6567,9 @@ write_make_seq(ostream &out, Object *obj, const std::string &ClassName,
     "\n";
     "\n";
 
 
   if ((elem_getter->_args_type & AT_varargs) == AT_varargs) {
   if ((elem_getter->_args_type & AT_varargs) == AT_varargs) {
+    out << "#if defined(Py_TRACE_REFS) || PY_VERSION_HEX < 0x03090000\n";
     out << "  _Py_ForgetReference((PyObject *)&args);\n";
     out << "  _Py_ForgetReference((PyObject *)&args);\n";
+    out << "#endif\n";
   }
   }
 
 
   out <<
   out <<

+ 11 - 3
dtool/src/interrogatedb/py_panda.cxx

@@ -10,6 +10,9 @@
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 
 
+#define _STRINGIFY_VERSION(a, b) (#a "." #b)
+#define STRINGIFY_VERSION(a, b) _STRINGIFY_VERSION(a, b)
+
 using std::string;
 using std::string;
 
 
 /**
 /**
@@ -521,6 +524,8 @@ Dtool_TypeMap *Dtool_GetGlobalTypeMap() {
   }
   }
 }
 }
 
 
+#define PY_MAJOR_VERSION_STR #PY_MAJOR_VERSION "." #PY_MINOR_VERSION
+
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
 PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def) {
 PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def) {
 #else
 #else
@@ -528,15 +533,18 @@ PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulen
 #endif
 #endif
   // Check the version so we can print a helpful error if it doesn't match.
   // Check the version so we can print a helpful error if it doesn't match.
   string version = Py_GetVersion();
   string version = Py_GetVersion();
+  size_t version_len = version.find('.', 2);
+  if (version_len != string::npos) {
+    version.resize(version_len);
+  }
 
 
-  if (version[0] != '0' + PY_MAJOR_VERSION ||
-      version[2] != '0' + PY_MINOR_VERSION) {
+  if (version != STRINGIFY_VERSION(PY_MAJOR_VERSION, PY_MINOR_VERSION)) {
     // Raise a helpful error message.  We can safely do this because the
     // Raise a helpful error message.  We can safely do this because the
     // signature and behavior for PyErr_SetString has remained consistent.
     // signature and behavior for PyErr_SetString has remained consistent.
     std::ostringstream errs;
     std::ostringstream errs;
     errs << "this module was compiled for Python "
     errs << "this module was compiled for Python "
          << PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is "
          << PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is "
-         << "incompatible with Python " << version.substr(0, 3);
+         << "incompatible with Python " << version;
     string error = errs.str();
     string error = errs.str();
     PyErr_SetString(PyExc_ImportError, error.c_str());
     PyErr_SetString(PyExc_ImportError, error.c_str());
     return nullptr;
     return nullptr;

+ 6 - 6
dtool/src/prc/configPageManager.I

@@ -12,7 +12,7 @@
  */
  */
 
 
 /**
 /**
- * Returns true if the implicit *.prc files have already been loaded, false
+ * Returns true if the implicit `*.prc` files have already been loaded, false
  * otherwise.  Normally this will only be false briefly before startup.
  * otherwise.  Normally this will only be false briefly before startup.
  */
  */
 INLINE bool ConfigPageManager::
 INLINE bool ConfigPageManager::
@@ -21,7 +21,7 @@ loaded_implicit_pages() const {
 }
 }
 
 
 /**
 /**
- * Searches the PRC_DIR and/or PRC_PATH directories for *.prc files and loads
+ * Searches the PRC_DIR and/or PRC_PATH directories for `*.prc` files and loads
  * them in as pages.  This is normally called automatically at startup time,
  * them in as pages.  This is normally called automatically at startup time,
  * when the first variable's value is referenced.  See also
  * when the first variable's value is referenced.  See also
  * reload_implicit_pages().
  * reload_implicit_pages().
@@ -46,9 +46,9 @@ get_search_path() {
 }
 }
 
 
 /**
 /**
- * Returns the number of patterns, like "*.prc", that are compiled in that
+ * Returns the number of patterns, like `*.prc`, that are compiled in that
  * will be searched for as default config filenames.  Normally there is only
  * will be searched for as default config filenames.  Normally there is only
- * one pattern, and it is "*.prc", but others may be specified with the
+ * one pattern, and it is `*.prc`, but others may be specified with the
  * PRC_FILENAME variable in Config.pp.
  * PRC_FILENAME variable in Config.pp.
  */
  */
 INLINE size_t ConfigPageManager::
 INLINE size_t ConfigPageManager::
@@ -67,7 +67,7 @@ get_prc_pattern(size_t n) const {
 }
 }
 
 
 /**
 /**
- * Returns the number of patterns, like "*.pre", that are compiled in that
+ * Returns the number of patterns, like `*.pre`, that are compiled in that
  * will be searched for as special config files that are understood to be
  * will be searched for as special config files that are understood to be
  * encrypted.
  * encrypted.
  */
  */
@@ -87,7 +87,7 @@ get_prc_encrypted_pattern(size_t n) const {
 }
 }
 
 
 /**
 /**
- * Returns the number of patterns, like "*.exe", that are compiled in that
+ * Returns the number of patterns, like `*.exe`, that are compiled in that
  * will be searched for as special config files that are to be executed as a
  * will be searched for as special config files that are to be executed as a
  * program, and their output taken to be input.  This is normally empty.
  * program, and their output taken to be input.  This is normally empty.
  */
  */

+ 2 - 2
dtool/src/prc/configVariableManager.cxx

@@ -82,7 +82,7 @@ make_variable(const string &name) {
  * Defines a variable "template" to match against dynamically-defined
  * Defines a variable "template" to match against dynamically-defined
  * variables that may or may not be created in the future.
  * variables that may or may not be created in the future.
  *
  *
- * The template consists of a glob pattern, e.g.  "notify-level-*", which will
+ * The template consists of a glob pattern, e.g.  `notify-level-*`, which will
  * be tested against any config variable passed to a future call to
  * be tested against any config variable passed to a future call to
  * make_variable().  If the pattern matches, the returned ConfigVariableCore
  * make_variable().  If the pattern matches, the returned ConfigVariableCore
  * is copied to define the new variable, instead of creating a default, empty
  * is copied to define the new variable, instead of creating a default, empty
@@ -92,7 +92,7 @@ make_variable(const string &name) {
  * all have similar properties, and all may not be created at the same time.
  * all have similar properties, and all may not be created at the same time.
  * It is especially useful to avoid cluttering up the list of available
  * It is especially useful to avoid cluttering up the list of available
  * variables with user-declared variables that have not been defined yet by
  * variables with user-declared variables that have not been defined yet by
- * the application (e.g.  "egg-object-type-*").
+ * the application (e.g. `egg-object-type-*`).
  *
  *
  * This method basically pre-defines all variables that match the specified
  * This method basically pre-defines all variables that match the specified
  * glob pattern.
  * glob pattern.

+ 3 - 3
dtool/src/prc/streamReader.h

@@ -68,10 +68,10 @@ PUBLISHED:
 
 
   BLOCKING void skip_bytes(size_t size);
   BLOCKING void skip_bytes(size_t size);
   BLOCKING size_t extract_bytes(unsigned char *into, size_t size);
   BLOCKING size_t extract_bytes(unsigned char *into, size_t size);
-  EXTENSION(BLOCKING PyObject *extract_bytes(size_t size));
+  EXTENSION(PyObject *extract_bytes(size_t size));
 
 
-  EXTENSION(BLOCKING PyObject *readline());
-  EXTENSION(BLOCKING PyObject *readlines());
+  EXTENSION(PyObject *readline());
+  EXTENSION(PyObject *readlines());
 
 
 public:
 public:
   BLOCKING vector_uchar extract_bytes(size_t size);
   BLOCKING vector_uchar extract_bytes(size_t size);

+ 60 - 9
dtool/src/prc/streamReader_ext.cxx

@@ -15,6 +15,8 @@
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 
 
+#include "vector_string.h"
+
 /**
 /**
  * Extracts the indicated number of bytes in the stream and returns them as a
  * Extracts the indicated number of bytes in the stream and returns them as a
  * string (or bytes, in Python 3).  Returns empty string at end-of-file.
  * string (or bytes, in Python 3).  Returns empty string at end-of-file.
@@ -23,13 +25,25 @@ PyObject *Extension<StreamReader>::
 extract_bytes(size_t size) {
 extract_bytes(size_t size) {
   std::istream *in = _this->get_istream();
   std::istream *in = _this->get_istream();
   if (in->eof() || in->fail() || size == 0) {
   if (in->eof() || in->fail() || size == 0) {
+    // Note that this is only safe to call with size 0 while the GIL is held.
     return PyBytes_FromStringAndSize(nullptr, 0);
     return PyBytes_FromStringAndSize(nullptr, 0);
   }
   }
 
 
   PyObject *bytes = PyBytes_FromStringAndSize(nullptr, size);
   PyObject *bytes = PyBytes_FromStringAndSize(nullptr, size);
-  in->read(PyBytes_AS_STRING(bytes), size);
+  char *buffer = (char *)PyBytes_AS_STRING(bytes);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
+
+  in->read(buffer, size);
   size_t read_bytes = in->gcount();
   size_t read_bytes = in->gcount();
 
 
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
+
   if (read_bytes == size || _PyBytes_Resize(&bytes, read_bytes) == 0) {
   if (read_bytes == size || _PyBytes_Resize(&bytes, read_bytes) == 0) {
     return bytes;
     return bytes;
   } else {
   } else {
@@ -47,6 +61,11 @@ extract_bytes(size_t size) {
  */
  */
 PyObject *Extension<StreamReader>::
 PyObject *Extension<StreamReader>::
 readline() {
 readline() {
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
+
   std::istream *in = _this->get_istream();
   std::istream *in = _this->get_istream();
 
 
   std::string line;
   std::string line;
@@ -60,6 +79,10 @@ readline() {
     ch = in->get();
     ch = in->get();
   }
   }
 
 
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
+
   return PyBytes_FromStringAndSize(line.data(), line.size());
   return PyBytes_FromStringAndSize(line.data(), line.size());
 }
 }
 
 
@@ -69,18 +92,46 @@ readline() {
  */
  */
 PyObject *Extension<StreamReader>::
 PyObject *Extension<StreamReader>::
 readlines() {
 readlines() {
-  PyObject *lst = PyList_New(0);
-  if (lst == nullptr) {
-    return nullptr;
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
+
+  std::istream *in = _this->get_istream();
+  vector_string lines;
+
+  while (true) {
+    std::string line;
+    int ch = in->get();
+    while (ch != EOF && !in->fail()) {
+      line += ch;
+      if (ch == '\n' || in->eof()) {
+        // Here's the newline character.
+        break;
+      }
+      ch = in->get();
+    }
+
+    if (line.empty()) {
+      break;
+    }
+
+    lines.push_back(std::move(line));
   }
   }
 
 
-  PyObject *py_line = readline();
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif  // HAVE_THREADS && !SIMPLE_THREADS
 
 
-  while (PyBytes_GET_SIZE(py_line) > 0) {
-    PyList_Append(lst, py_line);
-    Py_DECREF(py_line);
+  PyObject *lst = PyList_New(lines.size());
+  if (lst == nullptr) {
+    return nullptr;
+  }
 
 
-    py_line = readline();
+  Py_ssize_t i = 0;
+  for (const std::string &line : lines) {
+    PyObject *py_line = PyBytes_FromStringAndSize(line.data(), line.size());
+    PyList_SET_ITEM(lst, i++, py_line);
   }
   }
 
 
   return lst;
   return lst;

+ 3 - 3
dtool/src/prc/streamReader_ext.h

@@ -29,9 +29,9 @@
 template<>
 template<>
 class Extension<StreamReader> : public ExtensionBase<StreamReader> {
 class Extension<StreamReader> : public ExtensionBase<StreamReader> {
 public:
 public:
-  BLOCKING PyObject *extract_bytes(size_t size);
-  BLOCKING PyObject *readline();
-  BLOCKING PyObject *readlines();
+  PyObject *extract_bytes(size_t size);
+  PyObject *readline();
+  PyObject *readlines();
 };
 };
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 6 - 3
makepanda/makepackage.py

@@ -158,10 +158,13 @@ def MakeInstallerNSIS(version, file, title, installdir, compressor="lzma", **kwa
     # Are we shipping a version of Python?
     # Are we shipping a version of Python?
     if os.path.isfile(os.path.join(outputdir, "python", "python.exe")):
     if os.path.isfile(os.path.join(outputdir, "python", "python.exe")):
         py_dlls = glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9].dll")) \
         py_dlls = glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9].dll")) \
-                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9]_d.dll"))
+                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9]_d.dll")) \
+                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9][0-9].dll")) \
+                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9][0-9]_d.dll"))
         assert py_dlls
         assert py_dlls
         py_dll = os.path.basename(py_dlls[0])
         py_dll = os.path.basename(py_dlls[0])
-        pyver = py_dll[6] + "." + py_dll[7]
+        py_dllver = py_dll.strip(".DHLNOPTY_dhlnopty")
+        pyver = py_dllver[0] + '.' + py_dllver[1:]
 
 
         if GetTargetArch() != 'x64':
         if GetTargetArch() != 'x64':
             pyver += '-32'
             pyver += '-32'
@@ -723,7 +726,7 @@ def MakeInstallerFreeBSD(version, python_versions=[], **kwargs):
         oscmd("rm -f %s/tmp/python_dep" % outputdir)
         oscmd("rm -f %s/tmp/python_dep" % outputdir)
 
 
         if "PYTHONVERSION" in SDK:
         if "PYTHONVERSION" in SDK:
-            pyver_nodot = SDK["PYTHONVERSION"][6:9:2]
+            pyver_nodot = SDK["PYTHONVERSION"][6:].replace('.', '')
         else:
         else:
             pyver_nodot = "%d%d" % (sys.version_info[:2])
             pyver_nodot = "%d%d" % (sys.version_info[:2])
 
 

+ 20 - 12
makepanda/makepandacore.py

@@ -2058,9 +2058,11 @@ def SdkLocatePython(prefer_thirdparty_python=False):
 
 
         # Determine which version it is by checking which dll is in the directory.
         # Determine which version it is by checking which dll is in the directory.
         if (GetOptimize() <= 2):
         if (GetOptimize() <= 2):
-            py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]_d.dll")
+            py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]_d.dll") + \
+                      glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9]_d.dll")
         else:
         else:
-            py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9].dll")
+            py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9].dll") + \
+                      glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9].dll")
 
 
         if len(py_dlls) == 0:
         if len(py_dlls) == 0:
             exit("Could not find the Python dll in %s." % (SDK["PYTHON"]))
             exit("Could not find the Python dll in %s." % (SDK["PYTHON"]))
@@ -2068,24 +2070,29 @@ def SdkLocatePython(prefer_thirdparty_python=False):
             exit("Found multiple Python dlls in %s." % (SDK["PYTHON"]))
             exit("Found multiple Python dlls in %s." % (SDK["PYTHON"]))
 
 
         py_dll = os.path.basename(py_dlls[0])
         py_dll = os.path.basename(py_dlls[0])
-        ver = py_dll[6] + "." + py_dll[7]
+        py_dllver = py_dll.strip(".DHLNOPTY_dhlnopty")
+        ver = py_dllver[0] + '.' + py_dllver[1:]
 
 
         SDK["PYTHONVERSION"] = "python" + ver
         SDK["PYTHONVERSION"] = "python" + ver
         os.environ["PYTHONHOME"] = SDK["PYTHON"]
         os.environ["PYTHONHOME"] = SDK["PYTHON"]
 
 
-        if sys.version[:3] != ver:
-            Warn("running makepanda with Python %s, but building Panda3D with Python %s." % (sys.version[:3], ver))
+        running_ver = '%d.%d' % sys.version_info[:2]
+        if ver != running_ver:
+            Warn("running makepanda with Python %s, but building Panda3D with Python %s." % (running_ver, ver))
 
 
     elif CrossCompiling() or (prefer_thirdparty_python and os.path.isdir(os.path.join(GetThirdpartyDir(), "python"))):
     elif CrossCompiling() or (prefer_thirdparty_python and os.path.isdir(os.path.join(GetThirdpartyDir(), "python"))):
         tp_python = os.path.join(GetThirdpartyDir(), "python")
         tp_python = os.path.join(GetThirdpartyDir(), "python")
 
 
         if GetTarget() == 'darwin':
         if GetTarget() == 'darwin':
-            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].dylib")
+            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].dylib") + \
+                      glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].dylib")
         else:
         else:
-            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].so")
+            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].so") + \
+                      glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].so")
 
 
         if len(py_libs) == 0:
         if len(py_libs) == 0:
-            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].a")
+            py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].a") + \
+                      glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].a")
 
 
         if len(py_libs) == 0:
         if len(py_libs) == 0:
             exit("Could not find the Python library in %s." % (tp_python))
             exit("Could not find the Python library in %s." % (tp_python))
@@ -2093,7 +2100,8 @@ def SdkLocatePython(prefer_thirdparty_python=False):
             exit("Found multiple Python libraries in %s." % (tp_python))
             exit("Found multiple Python libraries in %s." % (tp_python))
 
 
         py_lib = os.path.basename(py_libs[0])
         py_lib = os.path.basename(py_libs[0])
-        SDK["PYTHONVERSION"] = "python" + py_lib[9] + "." + py_lib[11]
+        py_libver = py_lib.strip('.abdhilnopsty')
+        SDK["PYTHONVERSION"] = "python" + py_libver
         SDK["PYTHONEXEC"] = tp_python + "/bin/" + SDK["PYTHONVERSION"]
         SDK["PYTHONEXEC"] = tp_python + "/bin/" + SDK["PYTHONVERSION"]
         SDK["PYTHON"] = tp_python + "/include/" + SDK["PYTHONVERSION"]
         SDK["PYTHON"] = tp_python + "/include/" + SDK["PYTHONVERSION"]
 
 
@@ -2139,9 +2147,9 @@ def SdkLocatePython(prefer_thirdparty_python=False):
             exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
             exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
 
 
     if GetVerbose():
     if GetVerbose():
-        print("Using Python %s build located at %s" % (SDK["PYTHONVERSION"][6:9], SDK["PYTHON"]))
+        print("Using Python %s build located at %s" % (SDK["PYTHONVERSION"][6:], SDK["PYTHON"]))
     else:
     else:
-        print("Using Python %s" % (SDK["PYTHONVERSION"][6:9]))
+        print("Using Python %s" % (SDK["PYTHONVERSION"][6:]))
 
 
 def SdkLocateVisualStudio(version=(10,0)):
 def SdkLocateVisualStudio(version=(10,0)):
     if (GetHost() != "windows"): return
     if (GetHost() != "windows"): return
@@ -3352,7 +3360,7 @@ def GetCurrentPythonVersionInfo():
 
 
     from distutils.sysconfig import get_python_lib
     from distutils.sysconfig import get_python_lib
     return {
     return {
-        "version": SDK["PYTHONVERSION"][6:9],
+        "version": SDK["PYTHONVERSION"][6:],
         "soabi": GetPythonABI(),
         "soabi": GetPythonABI(),
         "ext_suffix": GetExtensionSuffix(),
         "ext_suffix": GetExtensionSuffix(),
         "executable": sys.executable,
         "executable": sys.executable,

+ 5 - 7
panda/src/audio/audioManager.h

@@ -106,13 +106,11 @@ PUBLISHED:
   virtual void set_volume(PN_stdfloat volume) = 0;
   virtual void set_volume(PN_stdfloat volume) = 0;
   virtual PN_stdfloat get_volume() const = 0;
   virtual PN_stdfloat get_volume() const = 0;
 
 
-/*
- * Turn the manager on or off.  If you play a sound while the manager is
- * inactive, it won't start.  If you deactivate the manager while sounds are
- * playing, they'll stop.  If you activate the manager while looping sounds
- * are playing (those that have a loop_count of zero), they will start playing
- * from the beginning of their loop.  inits to true.
- */
+  // Turn the manager on or off.  If you play a sound while the manager is
+  // inactive, it won't start.  If you deactivate the manager while sounds are
+  // playing, they'll stop.  If you activate the manager while looping sounds
+  // are playing (those that have a loop_count of zero), they will start
+  // playing from the beginning of their loop.  Defaults to true.
   virtual void set_active(bool flag) = 0;
   virtual void set_active(bool flag) = 0;
   virtual bool get_active() const = 0;
   virtual bool get_active() const = 0;
 
 

+ 3 - 3
panda/src/audio/audioSound.cxx

@@ -71,8 +71,8 @@ get_3d_max_distance() const {
  */
  */
 PN_stdfloat AudioSound::
 PN_stdfloat AudioSound::
 get_speaker_mix(int speaker) {
 get_speaker_mix(int speaker) {
-    // intentionally blank
-    return 0.0;
+  // intentionally blank
+  return 0.0;
 }
 }
 
 
 /**
 /**
@@ -80,7 +80,7 @@ get_speaker_mix(int speaker) {
  */
  */
 void AudioSound::
 void AudioSound::
 set_speaker_mix(PN_stdfloat frontleft, PN_stdfloat frontright, PN_stdfloat center, PN_stdfloat sub, PN_stdfloat backleft, PN_stdfloat backright, PN_stdfloat sideleft, PN_stdfloat  sideright) {
 set_speaker_mix(PN_stdfloat frontleft, PN_stdfloat frontright, PN_stdfloat center, PN_stdfloat sub, PN_stdfloat backleft, PN_stdfloat backright, PN_stdfloat sideleft, PN_stdfloat  sideright) {
-    // intentionally blank
+  // intentionally blank
 }
 }
 
 
 /**
 /**

+ 1 - 1
panda/src/audio/audioSound.h

@@ -108,7 +108,7 @@ PUBLISHED:
   virtual void set_3d_max_distance(PN_stdfloat dist);
   virtual void set_3d_max_distance(PN_stdfloat dist);
   virtual PN_stdfloat get_3d_max_distance() const;
   virtual PN_stdfloat get_3d_max_distance() const;
 
 
-  // *_speaker_mix is for use with FMOD.
+  // speaker_mix is for use with FMOD.
   virtual PN_stdfloat get_speaker_mix(int speaker);
   virtual PN_stdfloat get_speaker_mix(int speaker);
   virtual void set_speaker_mix(PN_stdfloat frontleft, PN_stdfloat frontright, PN_stdfloat center, PN_stdfloat sub, PN_stdfloat backleft, PN_stdfloat backright, PN_stdfloat sideleft, PN_stdfloat  sideright);
   virtual void set_speaker_mix(PN_stdfloat frontleft, PN_stdfloat frontright, PN_stdfloat center, PN_stdfloat sub, PN_stdfloat backleft, PN_stdfloat backright, PN_stdfloat sideleft, PN_stdfloat  sideright);
 
 

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

@@ -1732,6 +1732,19 @@ handle_key_event(NSEvent *event) {
     return;
     return;
   }
   }
 
 
+  // If UCKeyTranslate could not map the key into a valid unicode character or
+  // reserved symbol (See NSEvent.h), it returns 0x10 as translated character.
+  // This happens e.g.e with the combination Fn+F1.. keys.
+  // In that case, as fallback, we use charactersIgnoringModifiers to retrieve
+  // the character without modifiers.
+  if (c == 0x10) {
+    NSString *str = [event charactersIgnoringModifiers];
+    if (str == nil || [str length] != 1) {
+      return;
+    }
+    c = [str characterAtIndex: 0];
+  }
+
   ButtonHandle button = map_key(c);
   ButtonHandle button = map_key(c);
 
 
   if (button == ButtonHandle::none()) {
   if (button == ButtonHandle::none()) {

+ 1 - 1
panda/src/downloader/httpClient.cxx

@@ -663,7 +663,7 @@ get_proxies_for_url(const URLSpec &url) const {
  * Specifies the username:password string corresponding to a particular server
  * Specifies the username:password string corresponding to a particular server
  * and/or realm, when demanded by the server.  Either or both of the server or
  * and/or realm, when demanded by the server.  Either or both of the server or
  * realm may be empty; if so, they match anything.  Also, the server may be
  * realm may be empty; if so, they match anything.  Also, the server may be
- * set to the special string "*proxy", which will match any proxy server.
+ * set to the special string `"*proxy"`, which will match any proxy server.
  *
  *
  * If the username is set to the empty string, this clears the password for
  * If the username is set to the empty string, this clears the password for
  * the particular server/realm pair.
  * the particular server/realm pair.

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

@@ -509,6 +509,7 @@ has_alpha_channel(int num_components) const {
   case F_rgb8:
   case F_rgb8:
   case F_rgb5:
   case F_rgb5:
   case F_rgb332:
   case F_rgb332:
+  case F_srgb:
     // These formats never use alpha, regardless of the number of components
     // These formats never use alpha, regardless of the number of components
     // we have.
     // we have.
     return false;
     return false;
@@ -525,6 +526,7 @@ has_alpha_channel(int num_components) const {
   case F_rgba8:
   case F_rgba8:
   case F_rgba4:
   case F_rgba4:
   case F_rgba5:
   case F_rgba5:
+  case F_srgb_alpha:
   case F_unspecified:
   case F_unspecified:
     // These formats use alpha if the image had alpha.
     // These formats use alpha if the image had alpha.
     return (num_components == 2 || num_components == 4);
     return (num_components == 2 || num_components == 4);
@@ -690,6 +692,8 @@ EggTexture::Format EggTexture::
 string_format(const string &string) {
 string_format(const string &string) {
   if (cmp_nocase_uh(string, "rgba") == 0) {
   if (cmp_nocase_uh(string, "rgba") == 0) {
     return F_rgba;
     return F_rgba;
+  } else if (cmp_nocase_uh(string, "srgb_alpha") == 0) {
+    return F_srgb_alpha;
   } else if (cmp_nocase_uh(string, "rgbm") == 0) {
   } else if (cmp_nocase_uh(string, "rgbm") == 0) {
     return F_rgbm;
     return F_rgbm;
   } else if (cmp_nocase_uh(string, "rgba12") == 0) {
   } else if (cmp_nocase_uh(string, "rgba12") == 0) {
@@ -701,6 +705,8 @@ string_format(const string &string) {
 
 
   } else if (cmp_nocase_uh(string, "rgb") == 0) {
   } else if (cmp_nocase_uh(string, "rgb") == 0) {
     return F_rgb;
     return F_rgb;
+  } else if (cmp_nocase_uh(string, "srgb") == 0) {
+    return F_srgb;
   } else if (cmp_nocase_uh(string, "rgb12") == 0) {
   } else if (cmp_nocase_uh(string, "rgb12") == 0) {
     return F_rgb12;
     return F_rgb12;
   } else if (cmp_nocase_uh(string, "rgb8") == 0) {
   } else if (cmp_nocase_uh(string, "rgb8") == 0) {
@@ -1137,6 +1143,8 @@ ostream &operator << (ostream &out, EggTexture::Format format) {
     return out << "rgba8";
     return out << "rgba8";
   case EggTexture::F_rgba4:
   case EggTexture::F_rgba4:
     return out << "rgba4";
     return out << "rgba4";
+  case EggTexture::F_srgb_alpha:
+    return out << "srgb_alpha";
 
 
   case EggTexture::F_rgb:
   case EggTexture::F_rgb:
     return out << "rgb";
     return out << "rgb";
@@ -1150,6 +1158,8 @@ ostream &operator << (ostream &out, EggTexture::Format format) {
     return out << "rgba5";
     return out << "rgba5";
   case EggTexture::F_rgb332:
   case EggTexture::F_rgb332:
     return out << "rgb332";
     return out << "rgb332";
+  case EggTexture::F_srgb:
+    return out << "srgb";
 
 
   case EggTexture::F_red:
   case EggTexture::F_red:
     return out << "red";
     return out << "red";

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

@@ -60,7 +60,10 @@ PUBLISHED:
     F_rgba, F_rgbm, F_rgba12, F_rgba8, F_rgba4, F_rgba5,
     F_rgba, F_rgbm, F_rgba12, F_rgba8, F_rgba4, F_rgba5,
     F_rgb, F_rgb12, F_rgb8, F_rgb5, F_rgb332,
     F_rgb, F_rgb12, F_rgb8, F_rgb5, F_rgb332,
     F_red, F_green, F_blue, F_alpha, F_luminance,
     F_red, F_green, F_blue, F_alpha, F_luminance,
-    F_luminance_alpha, F_luminance_alphamask
+    F_luminance_alpha, F_luminance_alphamask,
+
+    // Only for compatibility with .bam, use is discouraged!
+    F_srgb, F_srgb_alpha
   };
   };
   enum CompressionMode {
   enum CompressionMode {
     CM_default, CM_off, CM_on,
     CM_default, CM_off, CM_on,

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

@@ -900,6 +900,7 @@ load_texture(TextureDef &def, EggTexture *egg_tex) {
   case EggTexture::F_rgb8:
   case EggTexture::F_rgb8:
   case EggTexture::F_rgb5:
   case EggTexture::F_rgb5:
   case EggTexture::F_rgb332:
   case EggTexture::F_rgb332:
+  case EggTexture::F_srgb:
     wanted_channels = 3;
     wanted_channels = 3;
     wanted_alpha = false;
     wanted_alpha = false;
     break;
     break;
@@ -910,6 +911,7 @@ load_texture(TextureDef &def, EggTexture *egg_tex) {
   case EggTexture::F_rgba8:
   case EggTexture::F_rgba8:
   case EggTexture::F_rgba4:
   case EggTexture::F_rgba4:
   case EggTexture::F_rgba5:
   case EggTexture::F_rgba5:
+  case EggTexture::F_srgb_alpha:
     wanted_channels = 4;
     wanted_channels = 4;
     wanted_alpha = true;
     wanted_alpha = true;
     break;
     break;
@@ -1259,6 +1261,10 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
     case EggTexture::F_rgb332:
     case EggTexture::F_rgb332:
       tex->set_format(Texture::F_rgb332);
       tex->set_format(Texture::F_rgb332);
       break;
       break;
+    case EggTexture::F_srgb:
+    case EggTexture::F_srgb_alpha:
+      tex->set_format(Texture::F_srgb);
+      break;
 
 
     case EggTexture::F_unspecified:
     case EggTexture::F_unspecified:
       break;
       break;
@@ -1296,6 +1302,9 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
     case EggTexture::F_rgba5:
     case EggTexture::F_rgba5:
       tex->set_format(Texture::F_rgba5);
       tex->set_format(Texture::F_rgba5);
       break;
       break;
+    case EggTexture::F_srgb_alpha:
+      tex->set_format(Texture::F_srgb_alpha);
+      break;
 
 
     case EggTexture::F_unspecified:
     case EggTexture::F_unspecified:
       break;
       break;

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

@@ -1411,6 +1411,12 @@ get_egg_texture(Texture *tex) {
       case Texture::F_luminance_alphamask:
       case Texture::F_luminance_alphamask:
         temp.set_format(EggTexture::F_luminance_alphamask);
         temp.set_format(EggTexture::F_luminance_alphamask);
         break;
         break;
+      case Texture::F_srgb:
+        temp.set_format(EggTexture::F_srgb);
+        break;
+      case Texture::F_srgb_alpha:
+        temp.set_format(EggTexture::F_srgb_alpha);
+        break;
       default:
       default:
         break;
         break;
       }
       }

+ 17 - 6
panda/src/egldisplay/eglGraphicsStateGuardian.cxx

@@ -104,9 +104,9 @@ get_properties(FrameBufferProperties &properties,
   properties.set_depth_bits(depth_size);
   properties.set_depth_bits(depth_size);
   properties.set_multisamples(samples);
   properties.set_multisamples(samples);
 
 
-  // Set both hardware and software bits, indicating not-yet-known.
-  properties.set_force_software(1);
-  properties.set_force_hardware(1);
+  // "slow" likely indicates no hardware acceleration.
+  properties.set_force_software(slow);
+  properties.set_force_hardware(!slow);
 }
 }
 
 
 /**
 /**
@@ -237,6 +237,20 @@ choose_pixel_format(const FrameBufferProperties &properties,
       if (!display || _visual)
       if (!display || _visual)
 #endif
 #endif
       {
       {
+        // This is set during window creation, but for now we have to pretend
+        // that we can honor the request, if we support the extension.
+        if (properties.get_srgb_color()) {
+          const char *extensions = eglQueryString(_egl_display, EGL_EXTENSIONS);
+          if (extensions != nullptr) {
+            vector_string tokens;
+            extract_words(extensions, tokens);
+
+            if (std::find(tokens.begin(), tokens.end(), "EGL_KHR_gl_colorspace") != tokens.end()) {
+              best_props.set_srgb_color(true);
+            }
+          }
+        }
+
         _fbprops = best_props;
         _fbprops = best_props;
         delete[] configs;
         delete[] configs;
         return;
         return;
@@ -269,9 +283,6 @@ reset() {
   if (_gl_renderer == "Software Rasterizer") {
   if (_gl_renderer == "Software Rasterizer") {
     _fbprops.set_force_software(1);
     _fbprops.set_force_software(1);
     _fbprops.set_force_hardware(0);
     _fbprops.set_force_hardware(0);
-  } else {
-    _fbprops.set_force_hardware(1);
-    _fbprops.set_force_software(0);
   }
   }
 }
 }
 
 

+ 18 - 1
panda/src/egldisplay/eglGraphicsWindow.cxx

@@ -30,6 +30,14 @@
 #include "nativeWindowHandle.h"
 #include "nativeWindowHandle.h"
 #include "get_x11.h"
 #include "get_x11.h"
 
 
+#ifndef EGL_GL_COLORSPACE_KHR
+#define EGL_GL_COLORSPACE_KHR 0x309D
+#endif
+
+#ifndef EGL_GL_COLORSPACE_SRGB_KHR
+#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089
+#endif
+
 TypeHandle eglGraphicsWindow::_type_handle;
 TypeHandle eglGraphicsWindow::_type_handle;
 
 
 /**
 /**
@@ -236,7 +244,16 @@ open_window() {
     return false;
     return false;
   }
   }
 
 
-  _egl_surface = eglCreateWindowSurface(_egl_display, eglgsg->_fbconfig, (NativeWindowType) _xwindow, nullptr);
+  EGLint attribs[4];
+  EGLint *attribs_p = nullptr;
+  if (eglgsg->get_fb_properties().get_srgb_color()) {
+    attribs[0] = EGL_GL_COLORSPACE_KHR;
+    attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR;
+    attribs[2] = EGL_NONE;
+    attribs[3] = EGL_NONE;
+    attribs_p = attribs;
+  }
+  _egl_surface = eglCreateWindowSurface(_egl_display, eglgsg->_fbconfig, (NativeWindowType) _xwindow, attribs_p);
   if (eglGetError() != EGL_SUCCESS) {
   if (eglGetError() != EGL_SUCCESS) {
     egldisplay_cat.error()
     egldisplay_cat.error()
       << "Failed to create window surface.\n";
       << "Failed to create window surface.\n";

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

@@ -100,9 +100,7 @@ typedef char GLchar;
 #define GL_RGBA16F GL_RGBA16F_EXT
 #define GL_RGBA16F GL_RGBA16F_EXT
 #define GL_RGB32F GL_RGB32F_EXT
 #define GL_RGB32F GL_RGB32F_EXT
 #define GL_RGBA32F GL_RGBA32F_EXT
 #define GL_RGBA32F GL_RGBA32F_EXT
-#define GL_SRGB GL_SRGB_EXT
 #define GL_SRGB_ALPHA GL_SRGB_ALPHA_EXT
 #define GL_SRGB_ALPHA GL_SRGB_ALPHA_EXT
-#define GL_SRGB8_ALPHA8 GL_SRGB8_ALPHA8_EXT
 #define GL_RGBA8 GL_RGBA8_OES
 #define GL_RGBA8 GL_RGBA8_OES
 #define GL_R8 GL_R8_EXT
 #define GL_R8 GL_R8_EXT
 #define GL_RG8 GL_RG8_EXT
 #define GL_RG8 GL_RG8_EXT
@@ -194,6 +192,9 @@ typedef char GLchar;
 #define GL_RGB9_E5 0x8C3D
 #define GL_RGB9_E5 0x8C3D
 #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
 #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
 #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
 #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
+#define GL_SRGB 0x8C40
+#define GL_SRGB8 0x8C41
+#define GL_SRGB8_ALPHA8 0x8C43
 #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
 #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
 #define GL_RED_INTEGER 0x8D94
 #define GL_RED_INTEGER 0x8D94
 #define GL_RGB_INTEGER 0x8D98
 #define GL_RGB_INTEGER 0x8D98

+ 20 - 18
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -170,6 +170,7 @@ static const string default_vshader =
   "out vec2 texcoord;\n"
   "out vec2 texcoord;\n"
   "out vec4 color;\n"
   "out vec4 color;\n"
 #else
 #else
+  "#version 100\n"
   "precision mediump float;\n"
   "precision mediump float;\n"
   "attribute vec4 p3d_Vertex;\n"
   "attribute vec4 p3d_Vertex;\n"
   "attribute vec4 p3d_Color;\n"
   "attribute vec4 p3d_Color;\n"
@@ -240,6 +241,7 @@ static const string default_fshader =
   "uniform sampler2D p3d_Texture0;\n"
   "uniform sampler2D p3d_Texture0;\n"
   "uniform vec4 p3d_TexAlphaOnly;\n"
   "uniform vec4 p3d_TexAlphaOnly;\n"
 #else
 #else
+  "#version 100\n"
   "precision mediump float;\n"
   "precision mediump float;\n"
   "varying vec2 texcoord;\n"
   "varying vec2 texcoord;\n"
   "varying lowp vec4 color;\n"
   "varying lowp vec4 color;\n"
@@ -3992,12 +3994,6 @@ prepare_lens() {
   }
   }
 #endif
 #endif
 
 
-#ifndef OPENGLES_1
-  if (_current_shader_context) {
-    _current_shader_context->issue_parameters(Shader::SSD_transform);
-  }
-#endif
-
   return true;
   return true;
 }
 }
 
 
@@ -10512,11 +10508,9 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
   case Texture::F_srgb:
   case Texture::F_srgb:
-#ifndef OPENGLES
-    return GL_SRGB8;
-#endif
+    return _supports_texture_srgb ? GL_SRGB8 : GL_RGB8;
   case Texture::F_srgb_alpha:
   case Texture::F_srgb_alpha:
-    return GL_SRGB8_ALPHA8;
+    return _supports_texture_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 #endif
 #endif
 #ifndef OPENGLES
 #ifndef OPENGLES
   case Texture::F_sluminance:
   case Texture::F_sluminance:
@@ -11405,9 +11399,10 @@ set_state_and_transform(const RenderState *target,
     do_issue_transform();
     do_issue_transform();
   }
   }
 
 
-  if (target == _state_rs && (_state_mask | _inv_state_mask).is_all_on()) {
-    return;
-  }
+  //XXX the _inv_state_mask system does not appear to be used at the moment.
+  //if (target == _state_rs && (_state_mask | _inv_state_mask).is_all_on()) {
+  //  return;
+  //}
   _target_rs = target;
   _target_rs = target;
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
@@ -13005,11 +13000,20 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
     int num_levels = 1;
     int num_levels = 1;
     CPTA_uchar image = tex->get_ram_mipmap_image(mipmap_bias);
     CPTA_uchar image = tex->get_ram_mipmap_image(mipmap_bias);
 
 
+    bool can_generate = _supports_generate_mipmap;
+#if defined(OPENGLES) && !defined(OPENGLES_1)
+    // OpenGL ES doesn't support generating mipmaps for sRGB textures, so we
+    // have to do this on the CPU, unless we have a special extension.
+    if (internal_format == GL_SRGB8 || internal_format == GL_SRGB8_ALPHA8) {
+      can_generate = has_extension("GL_NV_generate_mipmap_sRGB");
+    }
+#endif
+
     if (image.is_null()) {
     if (image.is_null()) {
       // We don't even have a RAM image, so we have no choice but to let
       // We don't even have a RAM image, so we have no choice but to let
       // mipmaps be generated on the GPU.
       // mipmaps be generated on the GPU.
       if (uses_mipmaps) {
       if (uses_mipmaps) {
-        if (_supports_generate_mipmap) {
+        if (can_generate) {
           num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
           num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
           gtc->_generate_mipmaps = true;
           gtc->_generate_mipmaps = true;
         } else {
         } else {
@@ -13025,7 +13029,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
 
 
         if (num_levels <= 1) {
         if (num_levels <= 1) {
           // No RAM mipmap levels available.  Should we generate some?
           // No RAM mipmap levels available.  Should we generate some?
-          if (!_supports_generate_mipmap || !driver_generate_mipmaps ||
+          if (!can_generate || !driver_generate_mipmaps ||
               image_compression != Texture::CM_off) {
               image_compression != Texture::CM_off) {
             // Yes, the GL can't or won't generate them, so we need to.  Note
             // Yes, the GL can't or won't generate them, so we need to.  Note
             // that some drivers (nVidia) will *corrupt memory* if you ask
             // that some drivers (nVidia) will *corrupt memory* if you ask
@@ -13038,7 +13042,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
         if (num_levels <= 1) {
         if (num_levels <= 1) {
           // We don't have mipmap levels in RAM.  Ask the GL to generate them
           // We don't have mipmap levels in RAM.  Ask the GL to generate them
           // if it can.
           // if it can.
-          if (_supports_generate_mipmap) {
+          if (can_generate) {
             num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
             num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
             gtc->_generate_mipmaps = true;
             gtc->_generate_mipmaps = true;
           } else {
           } else {
@@ -14304,9 +14308,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
   case GL_SRGB:
   case GL_SRGB:
-#ifndef OPENGLES
   case GL_SRGB8:
   case GL_SRGB8:
-#endif
     format = Texture::F_srgb;
     format = Texture::F_srgb;
     break;
     break;
   case GL_SRGB_ALPHA:
   case GL_SRGB_ALPHA:

+ 1 - 1
panda/src/linmath/lmatrix4_src.I

@@ -1325,7 +1325,7 @@ invert_in_place() {
 }
 }
 
 
 /**
 /**
- * Computes (*this) += other * weight.
+ * Computes `(*this) += other * weight`.
  */
  */
 INLINE_LINMATH void FLOATNAME(LMatrix4)::
 INLINE_LINMATH void FLOATNAME(LMatrix4)::
 accumulate(const FLOATNAME(LMatrix4) &other, FLOATTYPE weight) {
 accumulate(const FLOATNAME(LMatrix4) &other, FLOATTYPE weight) {

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

@@ -1045,17 +1045,25 @@ make_style(const WindowProperties &properties) {
   // Additionally, the window class attribute should not include the
   // Additionally, the window class attribute should not include the
   // CS_PARENTDC style.
   // CS_PARENTDC style.
 
 
-  DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+  DWORD window_style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
 
 
   if (properties.get_fullscreen()) {
   if (properties.get_fullscreen()) {
-    window_style |= WS_SYSMENU;
-  } else if (!properties.get_undecorated()) {
-    window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
-
-    if (!properties.get_fixed_size()) {
-      window_style |= (WS_SIZEBOX | WS_MAXIMIZEBOX);
+    window_style |= WS_POPUP | WS_SYSMENU;
+  } else {
+    if (_parent_window_handle) {
+      window_style |= WS_CHILD;
     } else {
     } else {
-      window_style |= WS_BORDER;
+      window_style |= WS_POPUP;
+    }
+
+    if (!properties.get_undecorated()) {
+      window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
+
+      if (!properties.get_fixed_size()) {
+        window_style |= (WS_SIZEBOX | WS_MAXIMIZEBOX);
+      } else {
+        window_style |= WS_BORDER;
+      }
     }
     }
   }
   }
   return window_style;
   return window_style;

+ 69 - 1
pandatool/src/deploy-stub/deploy-stub.c

@@ -24,6 +24,8 @@
 
 
 #include <locale.h>
 #include <locale.h>
 
 
+#include "structmember.h"
+
 /* Leave room for future expansion.  We only read pointer 0, but there are
 /* Leave room for future expansion.  We only read pointer 0, but there are
    other pointers that are being read by configPageManager.cxx. */
    other pointers that are being read by configPageManager.cxx. */
 #define MAX_NUM_POINTERS 24
 #define MAX_NUM_POINTERS 24
@@ -335,6 +337,49 @@ static int setup_logging(const char *path, int append) {
 #endif
 #endif
 }
 }
 
 
+/**
+ * Sets the line_buffering property on a TextIOWrapper object.
+ */
+static int enable_line_buffering(PyObject *file) {
+#if PY_VERSION_HEX >= 0x03070000
+  /* Python 3.7 has a useful reconfigure() method. */
+  PyObject *kwargs = _PyDict_NewPresized(1);
+  PyDict_SetItemString(kwargs, "line_buffering", Py_True);
+  PyObject *args = PyTuple_New(0);
+
+  PyObject *method = PyObject_GetAttrString(file, "reconfigure");
+  if (method != NULL) {
+    PyObject *result = PyObject_Call(method, args, kwargs);
+    Py_DECREF(method);
+    if (result != NULL) {
+      Py_DECREF(result);
+    } else {
+      PyErr_Clear();
+      return 0;
+    }
+  }
+  Py_DECREF(kwargs);
+  Py_DECREF(args);
+#else
+  /* Older versions just don't expose a way to reconfigure(), but it's still
+     safe to override the property; we just have to use a hack to do it,
+     because it's officially marked "readonly". */
+
+  PyTypeObject *type = Py_TYPE(file);
+  PyMemberDef *member = type->tp_members;
+
+  while (member != NULL && member->name != NULL) {
+    if (strcmp(member->name, "line_buffering") == 0) {
+      *((char *)file + member->offset) = 1;
+      return 1;
+    }
+    ++member;
+  }
+  fflush(stdout);
+#endif
+  return 1;
+}
+
 /* Main program */
 /* Main program */
 
 
 #ifdef WIN_UNICODE
 #ifdef WIN_UNICODE
@@ -345,8 +390,10 @@ int Py_FrozenMain(int argc, char **argv)
 {
 {
     char *p;
     char *p;
     int n, sts = 1;
     int n, sts = 1;
-    int inspect = 0;
     int unbuffered = 0;
     int unbuffered = 0;
+#ifndef NDEBUG
+    int inspect = 0;
+#endif
 
 
 #ifndef WIN_UNICODE
 #ifndef WIN_UNICODE
     int i;
     int i;
@@ -378,8 +425,10 @@ int Py_FrozenMain(int argc, char **argv)
     Py_NoSiteFlag = 0;
     Py_NoSiteFlag = 0;
     Py_NoUserSiteDirectory = 1;
     Py_NoUserSiteDirectory = 1;
 
 
+#ifndef NDEBUG
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
         inspect = 1;
         inspect = 1;
+#endif
     if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
     if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
         unbuffered = 1;
         unbuffered = 1;
 
 
@@ -422,6 +471,23 @@ int Py_FrozenMain(int argc, char **argv)
     PyWinFreeze_ExeInit();
     PyWinFreeze_ExeInit();
 #endif
 #endif
 
 
+#ifdef MS_WINDOWS
+    /* Ensure that line buffering is enabled on the output streams. */
+    if (!unbuffered) {
+      PyObject *sys_stream;
+      sys_stream = PySys_GetObject("__stdout__");
+      if (sys_stream && !enable_line_buffering(sys_stream)) {
+        fprintf(stderr, "Failed to enable line buffering on sys.stdout\n");
+        fflush(stderr);
+      }
+      sys_stream = PySys_GetObject("__stderr__");
+      if (sys_stream && !enable_line_buffering(sys_stream)) {
+        fprintf(stderr, "Failed to enable line buffering on sys.stderr\n");
+        fflush(stderr);
+      }
+    }
+#endif
+
     if (Py_VerboseFlag)
     if (Py_VerboseFlag)
         fprintf(stderr, "Python %s\n%s\n",
         fprintf(stderr, "Python %s\n%s\n",
             Py_GetVersion(), Py_GetCopyright());
             Py_GetVersion(), Py_GetCopyright());
@@ -473,8 +539,10 @@ int Py_FrozenMain(int argc, char **argv)
     else
     else
         sts = 0;
         sts = 0;
 
 
+#ifndef NDEBUG
     if (inspect && isatty((int)fileno(stdin)))
     if (inspect && isatty((int)fileno(stdin)))
         sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
         sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
+#endif
 
 
 #ifdef MS_WINDOWS
 #ifdef MS_WINDOWS
     PyWinFreeze_ExeTerm();
     PyWinFreeze_ExeTerm();

+ 2 - 0
pandatool/src/palettizer/textureProperties.cxx

@@ -244,6 +244,7 @@ fully_define() {
     case EggTexture::F_rgba8:
     case EggTexture::F_rgba8:
     case EggTexture::F_rgba4:
     case EggTexture::F_rgba4:
     case EggTexture::F_rgba5:
     case EggTexture::F_rgba5:
+    case EggTexture::F_srgb_alpha:
       _num_channels = 4;
       _num_channels = 4;
       break;
       break;
 
 
@@ -253,6 +254,7 @@ fully_define() {
     case EggTexture::F_rgb8:
     case EggTexture::F_rgb8:
     case EggTexture::F_rgb5:
     case EggTexture::F_rgb5:
     case EggTexture::F_rgb332:
     case EggTexture::F_rgb332:
+    case EggTexture::F_srgb:
       _num_channels = 3;
       _num_channels = 3;
       break;
       break;