瀏覽代碼

Merge branch 'release/1.10.x' into master

rdb 5 年之前
父節點
當前提交
66ac3be604
共有 38 個文件被更改,包括 494 次插入127 次删除
  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
  *   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,
- *   use get_shadow_source(n)->set_needs_update(true).
+ *   use `get_shadow_source(n)->set_needs_update(true)`.
  */
 inline void RPLight::invalidate_shadows() {
   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 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
  */
@@ -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
  *   handle previously returned by RenderPipeline.load_ies_profile. Using a
  *   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
  */
@@ -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.
  *
  * @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.
  *
- * @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 {
   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() {
   set_ies_profile(-1);
@@ -377,7 +377,7 @@ inline void RPLight::clear_ies_profile() {
  * @brief Sets the near plane of the light
  * @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
- *   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
  *   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
- * @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
  *   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']
 

+ 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.
 It loads and controls an animated character and manages the animations
 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 \
                          dtool/src/cppparser \
                          dtool/src/interrogate \
+                         direct/src/directscripts \
                          direct/src/plugin \
                          direct/src/plugin_standalone \
                          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))
 
         pip_args = [
+            '--disable-pip-version-check',
             'download',
             '-d', whldir,
             '-r', self.requirements_path,
             '--only-binary', ':all:',
             '--platform', platform,
-            '--abi', abi_tag
+            '--abi', abi_tag,
         ]
 
         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
 API, a high-level networking system that automatically propagates
 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
-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
 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 AuxBitplaneAttrib
 from panda3d.core import Texture, Shader, ATSNone
+from panda3d.core import FrameBufferProperties
 import os
 
 CARTOON_BODY="""
@@ -177,7 +179,15 @@ class CommonFilters:
                 self.textures[tex].setWrapU(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):
                 self.cleanup()
                 return False
@@ -255,6 +265,17 @@ class CommonFilters:
             texcoordSets = list(enumerate(texcoordPadding.keys()))
 
             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 += "  out float4 l_position : POSITION,\n"
 
@@ -301,6 +322,10 @@ class CommonFilters:
             if ("VolumetricLighting" in configuration):
                 text += "  uniform float4 k_casterpos,\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 += "{\n"
             text += "  o_color = tex2D(k_txcolor, %s);\n" % (texcoords["color"])
@@ -332,6 +357,14 @@ class CommonFilters:
                 text += "  }\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):
                 gamma = configuration["GammaAdjust"]
                 if gamma == 0.5:
@@ -341,6 +374,11 @@ class CommonFilters:
                 elif gamma != 1.0:
                     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):
                 text += "  o_color = float4(1, 1, 1, 1) - o_color;\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("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()
         return True
 
@@ -547,6 +590,73 @@ class CommonFilters:
             return self.reconfigure((old_gamma != 1.0), "GammaAdjust")
         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:
     del_cartoon_ink = delCartoonInk
     set_half_pixel_shift = setHalfPixelShift
@@ -555,7 +665,6 @@ class CommonFilters:
     del_inverted = delInverted
     del_view_glow = delViewGlow
     set_volumetric_lighting = setVolumetricLighting
-    del_gamma_adjust = delGammaAdjust
     set_bloom = setBloom
     set_view_glow = setViewGlow
     set_ambient_occlusion = setAmbientOcclusion
@@ -566,3 +675,10 @@ class CommonFilters:
     del_blur_sharpen = delBlurSharpen
     del_volumetric_lighting = delVolumetricLighting
     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
 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:
 
 * 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 OrthographicLens
 from panda3d.core import AuxBitplaneAttrib
+from panda3d.core import LightRampAttrib
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.DirectObject import DirectObject
 
@@ -124,7 +127,7 @@ class FilterManager(DirectObject):
 
         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
         instead of into the original window.  Puts a fullscreen quad
@@ -207,6 +210,9 @@ class FilterManager(DirectObject):
         #cs.setShaderAuto()
         if (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())
 
         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
 provided out of the box, whereas the :class:`.FilterManager` class
 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
 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
-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']
@@ -13,6 +20,9 @@ import Pmw
 ### public API
 
 def inspect(anObject):
+    """Opens up a window for visually inspecting the details of a given Python
+    object.  See :ref:`inspection-utilities`.
+    """
     inspector = inspectorFor(anObject)
     inspectorWindow = InspectorWindow(inspector)
     inspectorWindow.open()
@@ -437,7 +447,3 @@ class InspectorWindow:
                 label = item,
                 command = lambda p = part, f = func: f(p))
         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";
 
   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 << "#endif\n";
   }
 
   out <<

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

@@ -10,6 +10,9 @@
 
 #ifdef HAVE_PYTHON
 
+#define _STRINGIFY_VERSION(a, b) (#a "." #b)
+#define STRINGIFY_VERSION(a, b) _STRINGIFY_VERSION(a, b)
+
 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
 PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def) {
 #else
@@ -528,15 +533,18 @@ PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulen
 #endif
   // Check the version so we can print a helpful error if it doesn't match.
   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
     // signature and behavior for PyErr_SetString has remained consistent.
     std::ostringstream errs;
     errs << "this module was compiled for Python "
          << PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is "
-         << "incompatible with Python " << version.substr(0, 3);
+         << "incompatible with Python " << version;
     string error = errs.str();
     PyErr_SetString(PyExc_ImportError, error.c_str());
     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.
  */
 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,
  * when the first variable's value is referenced.  See also
  * 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
- * 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.
  */
 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
  * 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
  * 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
  * 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
  * make_variable().  If the pattern matches, the returned ConfigVariableCore
  * 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.
  * It is especially useful to avoid cluttering up the list of available
  * 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
  * glob pattern.

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

@@ -68,10 +68,10 @@ PUBLISHED:
 
   BLOCKING void skip_bytes(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:
   BLOCKING vector_uchar extract_bytes(size_t size);

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

@@ -15,6 +15,8 @@
 
 #ifdef HAVE_PYTHON
 
+#include "vector_string.h"
+
 /**
  * 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.
@@ -23,13 +25,25 @@ PyObject *Extension<StreamReader>::
 extract_bytes(size_t size) {
   std::istream *in = _this->get_istream();
   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);
   }
 
   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();
 
+#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) {
     return bytes;
   } else {
@@ -47,6 +61,11 @@ extract_bytes(size_t size) {
  */
 PyObject *Extension<StreamReader>::
 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::string line;
@@ -60,6 +79,10 @@ readline() {
     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());
 }
 
@@ -69,18 +92,46 @@ readline() {
  */
 PyObject *Extension<StreamReader>::
 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;

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

@@ -29,9 +29,9 @@
 template<>
 class Extension<StreamReader> : public ExtensionBase<StreamReader> {
 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

+ 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?
     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")) \
-                + 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
         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':
             pyver += '-32'
@@ -723,7 +726,7 @@ def MakeInstallerFreeBSD(version, python_versions=[], **kwargs):
         oscmd("rm -f %s/tmp/python_dep" % outputdir)
 
         if "PYTHONVERSION" in SDK:
-            pyver_nodot = SDK["PYTHONVERSION"][6:9:2]
+            pyver_nodot = SDK["PYTHONVERSION"][6:].replace('.', '')
         else:
             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.
         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:
-            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:
             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"]))
 
         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
         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"))):
         tp_python = os.path.join(GetThirdpartyDir(), "python")
 
         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:
-            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:
-            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:
             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))
 
         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["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"]))
 
     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:
-        print("Using Python %s" % (SDK["PYTHONVERSION"][6:9]))
+        print("Using Python %s" % (SDK["PYTHONVERSION"][6:]))
 
 def SdkLocateVisualStudio(version=(10,0)):
     if (GetHost() != "windows"): return
@@ -3352,7 +3360,7 @@ def GetCurrentPythonVersionInfo():
 
     from distutils.sysconfig import get_python_lib
     return {
-        "version": SDK["PYTHONVERSION"][6:9],
+        "version": SDK["PYTHONVERSION"][6:],
         "soabi": GetPythonABI(),
         "ext_suffix": GetExtensionSuffix(),
         "executable": sys.executable,

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

@@ -106,13 +106,11 @@ PUBLISHED:
   virtual void set_volume(PN_stdfloat volume) = 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 bool get_active() const = 0;
 

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

@@ -71,8 +71,8 @@ get_3d_max_distance() const {
  */
 PN_stdfloat AudioSound::
 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::
 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 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 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;
   }
 
+  // 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);
 
   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
  * 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
- * 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
  * 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_rgb5:
   case F_rgb332:
+  case F_srgb:
     // These formats never use alpha, regardless of the number of components
     // we have.
     return false;
@@ -525,6 +526,7 @@ has_alpha_channel(int num_components) const {
   case F_rgba8:
   case F_rgba4:
   case F_rgba5:
+  case F_srgb_alpha:
   case F_unspecified:
     // These formats use alpha if the image had alpha.
     return (num_components == 2 || num_components == 4);
@@ -690,6 +692,8 @@ EggTexture::Format EggTexture::
 string_format(const string &string) {
   if (cmp_nocase_uh(string, "rgba") == 0) {
     return F_rgba;
+  } else if (cmp_nocase_uh(string, "srgb_alpha") == 0) {
+    return F_srgb_alpha;
   } else if (cmp_nocase_uh(string, "rgbm") == 0) {
     return F_rgbm;
   } 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) {
     return F_rgb;
+  } else if (cmp_nocase_uh(string, "srgb") == 0) {
+    return F_srgb;
   } else if (cmp_nocase_uh(string, "rgb12") == 0) {
     return F_rgb12;
   } else if (cmp_nocase_uh(string, "rgb8") == 0) {
@@ -1137,6 +1143,8 @@ ostream &operator << (ostream &out, EggTexture::Format format) {
     return out << "rgba8";
   case EggTexture::F_rgba4:
     return out << "rgba4";
+  case EggTexture::F_srgb_alpha:
+    return out << "srgb_alpha";
 
   case EggTexture::F_rgb:
     return out << "rgb";
@@ -1150,6 +1158,8 @@ ostream &operator << (ostream &out, EggTexture::Format format) {
     return out << "rgba5";
   case EggTexture::F_rgb332:
     return out << "rgb332";
+  case EggTexture::F_srgb:
+    return out << "srgb";
 
   case EggTexture::F_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_rgb, F_rgb12, F_rgb8, F_rgb5, F_rgb332,
     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 {
     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_rgb5:
   case EggTexture::F_rgb332:
+  case EggTexture::F_srgb:
     wanted_channels = 3;
     wanted_alpha = false;
     break;
@@ -910,6 +911,7 @@ load_texture(TextureDef &def, EggTexture *egg_tex) {
   case EggTexture::F_rgba8:
   case EggTexture::F_rgba4:
   case EggTexture::F_rgba5:
+  case EggTexture::F_srgb_alpha:
     wanted_channels = 4;
     wanted_alpha = true;
     break;
@@ -1259,6 +1261,10 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
     case EggTexture::F_rgb332:
       tex->set_format(Texture::F_rgb332);
       break;
+    case EggTexture::F_srgb:
+    case EggTexture::F_srgb_alpha:
+      tex->set_format(Texture::F_srgb);
+      break;
 
     case EggTexture::F_unspecified:
       break;
@@ -1296,6 +1302,9 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
     case EggTexture::F_rgba5:
       tex->set_format(Texture::F_rgba5);
       break;
+    case EggTexture::F_srgb_alpha:
+      tex->set_format(Texture::F_srgb_alpha);
+      break;
 
     case EggTexture::F_unspecified:
       break;

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

@@ -1411,6 +1411,12 @@ get_egg_texture(Texture *tex) {
       case Texture::F_luminance_alphamask:
         temp.set_format(EggTexture::F_luminance_alphamask);
         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:
         break;
       }

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

@@ -104,9 +104,9 @@ get_properties(FrameBufferProperties &properties,
   properties.set_depth_bits(depth_size);
   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)
 #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;
         delete[] configs;
         return;
@@ -269,9 +283,6 @@ reset() {
   if (_gl_renderer == "Software Rasterizer") {
     _fbprops.set_force_software(1);
     _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 "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;
 
 /**
@@ -236,7 +244,16 @@ open_window() {
     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) {
     egldisplay_cat.error()
       << "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_RGB32F GL_RGB32F_EXT
 #define GL_RGBA32F GL_RGBA32F_EXT
-#define GL_SRGB GL_SRGB_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_R8 GL_R8_EXT
 #define GL_RG8 GL_RG8_EXT
@@ -194,6 +192,9 @@ typedef char GLchar;
 #define GL_RGB9_E5 0x8C3D
 #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
 #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_RED_INTEGER 0x8D94
 #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 vec4 color;\n"
 #else
+  "#version 100\n"
   "precision mediump float;\n"
   "attribute vec4 p3d_Vertex;\n"
   "attribute vec4 p3d_Color;\n"
@@ -240,6 +241,7 @@ static const string default_fshader =
   "uniform sampler2D p3d_Texture0;\n"
   "uniform vec4 p3d_TexAlphaOnly;\n"
 #else
+  "#version 100\n"
   "precision mediump float;\n"
   "varying vec2 texcoord;\n"
   "varying lowp vec4 color;\n"
@@ -3992,12 +3994,6 @@ prepare_lens() {
   }
 #endif
 
-#ifndef OPENGLES_1
-  if (_current_shader_context) {
-    _current_shader_context->issue_parameters(Shader::SSD_transform);
-  }
-#endif
-
   return true;
 }
 
@@ -10512,11 +10508,9 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
 
 #ifndef OPENGLES_1
   case Texture::F_srgb:
-#ifndef OPENGLES
-    return GL_SRGB8;
-#endif
+    return _supports_texture_srgb ? GL_SRGB8 : GL_RGB8;
   case Texture::F_srgb_alpha:
-    return GL_SRGB8_ALPHA8;
+    return _supports_texture_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 #endif
 #ifndef OPENGLES
   case Texture::F_sluminance:
@@ -11405,9 +11399,10 @@ set_state_and_transform(const RenderState *target,
     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;
 
 #ifndef OPENGLES_1
@@ -13005,11 +13000,20 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
     int num_levels = 1;
     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()) {
       // We don't even have a RAM image, so we have no choice but to let
       // mipmaps be generated on the GPU.
       if (uses_mipmaps) {
-        if (_supports_generate_mipmap) {
+        if (can_generate) {
           num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
           gtc->_generate_mipmaps = true;
         } else {
@@ -13025,7 +13029,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
 
         if (num_levels <= 1) {
           // 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) {
             // 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
@@ -13038,7 +13042,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
         if (num_levels <= 1) {
           // We don't have mipmap levels in RAM.  Ask the GL to generate them
           // if it can.
-          if (_supports_generate_mipmap) {
+          if (can_generate) {
             num_levels = tex->get_expected_num_mipmap_levels() - mipmap_bias;
             gtc->_generate_mipmaps = true;
           } else {
@@ -14304,9 +14308,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
 
 #ifndef OPENGLES_1
   case GL_SRGB:
-#ifndef OPENGLES
   case GL_SRGB8:
-#endif
     format = Texture::F_srgb;
     break;
   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)::
 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
   // CS_PARENTDC style.
 
-  DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+  DWORD window_style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
 
   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 {
-      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;

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

@@ -24,6 +24,8 @@
 
 #include <locale.h>
 
+#include "structmember.h"
+
 /* Leave room for future expansion.  We only read pointer 0, but there are
    other pointers that are being read by configPageManager.cxx. */
 #define MAX_NUM_POINTERS 24
@@ -335,6 +337,49 @@ static int setup_logging(const char *path, int append) {
 #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 */
 
 #ifdef WIN_UNICODE
@@ -345,8 +390,10 @@ int Py_FrozenMain(int argc, char **argv)
 {
     char *p;
     int n, sts = 1;
-    int inspect = 0;
     int unbuffered = 0;
+#ifndef NDEBUG
+    int inspect = 0;
+#endif
 
 #ifndef WIN_UNICODE
     int i;
@@ -378,8 +425,10 @@ int Py_FrozenMain(int argc, char **argv)
     Py_NoSiteFlag = 0;
     Py_NoUserSiteDirectory = 1;
 
+#ifndef NDEBUG
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
         inspect = 1;
+#endif
     if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
         unbuffered = 1;
 
@@ -422,6 +471,23 @@ int Py_FrozenMain(int argc, char **argv)
     PyWinFreeze_ExeInit();
 #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)
         fprintf(stderr, "Python %s\n%s\n",
             Py_GetVersion(), Py_GetCopyright());
@@ -473,8 +539,10 @@ int Py_FrozenMain(int argc, char **argv)
     else
         sts = 0;
 
+#ifndef NDEBUG
     if (inspect && isatty((int)fileno(stdin)))
         sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
+#endif
 
 #ifdef MS_WINDOWS
     PyWinFreeze_ExeTerm();

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

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