Browse Source

Merge branch 'master' into cmake

Sam Edwards 7 years ago
parent
commit
d4df1b3762
46 changed files with 615 additions and 270 deletions
  1. 9 11
      dtool/src/interrogate/interrogateBuilder.cxx
  2. 9 1
      dtool/src/interrogatedb/py_compat.h
  3. 57 4
      dtool/src/interrogatedb/py_panda.cxx
  4. 34 34
      dtool/src/interrogatedb/py_panda.h
  5. 6 6
      dtool/src/interrogatedb/py_wrappers.h
  6. 6 3
      makepanda/makepanda.py
  7. 7 2
      makepanda/makepandacore.py
  8. 16 16
      panda/src/androiddisplay/androidGraphicsWindow.cxx
  9. 1 1
      panda/src/androiddisplay/androidGraphicsWindow.h
  10. 78 5
      panda/src/device/evdevInputDevice.cxx
  11. 10 1
      panda/src/device/evdevInputDevice.h
  12. 75 0
      panda/src/device/linuxInputDeviceManager.cxx
  13. 3 0
      panda/src/device/linuxInputDeviceManager.h
  14. 20 5
      panda/src/device/linuxJoystickDevice.cxx
  15. 1 1
      panda/src/device/linuxJoystickDevice.h
  16. 41 1
      panda/src/device/winInputDeviceManager.cxx
  17. 63 46
      panda/src/device/winRawInputDevice.cxx
  18. 1 0
      panda/src/device/winRawInputDevice.h
  19. 12 0
      panda/src/device/xInputDevice.cxx
  20. 0 9
      panda/src/display/config_display.cxx
  21. 0 1
      panda/src/display/config_display.h
  22. 2 1
      panda/src/display/graphicsStateGuardian.cxx
  23. 5 44
      panda/src/egg2pg/eggSaver.cxx
  24. 0 2
      panda/src/express/config_express.N
  25. 10 0
      panda/src/glstuff/glCgShaderContext_src.cxx
  26. 2 0
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  27. 9 0
      panda/src/gobj/config_gobj.cxx
  28. 1 0
      panda/src/gobj/config_gobj.h
  29. 18 3
      panda/src/gobj/shader.cxx
  30. 2 0
      panda/src/gobj/shader.h
  31. 3 1
      panda/src/grutil/shaderTerrainMesh.cxx
  32. 8 45
      panda/src/pgraphnodes/lightLensNode.I
  33. 58 2
      panda/src/pgraphnodes/lightLensNode.cxx
  34. 6 2
      panda/src/pgraphnodes/lightLensNode.h
  35. 8 2
      panda/src/pgraphnodes/shaderGenerator.cxx
  36. 1 2
      panda/src/pipeline/lightReMutexDirect.h
  37. 1 2
      panda/src/pipeline/reMutexDirect.h
  38. 4 5
      panda/src/pnmimagetypes/pnmFileTypePNG.cxx
  39. 4 0
      panda/src/putil/gamepadButton.cxx
  40. 2 0
      panda/src/putil/gamepadButton.h
  41. 1 0
      panda/src/x11display/x11GraphicsPipe.cxx
  42. 1 0
      panda/src/x11display/x11GraphicsPipe.h
  43. 8 0
      panda/src/x11display/x11GraphicsWindow.cxx
  44. 8 10
      samples/cartoon-shader/normalGen.sha
  45. 2 2
      samples/fireflies/model.sha
  46. 2 0
      samples/gamepad/device_tester.py

+ 9 - 11
dtool/src/interrogate/interrogateBuilder.cxx

@@ -656,13 +656,13 @@ get_preferred_name(CPPType *type) {
  */
 string InterrogateBuilder::
 hash_string(const string &name, int shift_offset) {
-  int hash = 0;
+  unsigned int hash = 0;
 
-  int shift = 0;
+  unsigned int shift = 0;
   string::const_iterator ni;
   for (ni = name.begin(); ni != name.end(); ++ni) {
-    int c = (int)(unsigned char)(*ni);
-    int shifted_c = (c << shift) & 0xffffff;
+    unsigned int c = (unsigned char)*ni;
+    unsigned int shifted_c = (c << shift) & 0xffffff;
     if (shift > 16) {
       // We actually want a circular shift, not an arithmetic shift.
       shifted_c |= ((c >> (24 - shift)) & 0xff) ;
@@ -675,10 +675,9 @@ hash_string(const string &name, int shift_offset) {
   // bits back at the bottom, to scramble up the bits a bit.  This helps
   // reduce hash conflicts from names that are similar to each other, by
   // separating adjacent hash codes.
-  int prime = 4999;
-  int low_order = (hash * prime) & 0xffffff;
-  int high_order = (int)((double)hash * (double)prime / (double)(1 << 24));
-  hash = low_order ^ high_order;
+  const unsigned int prime = 4999;
+  unsigned long long product = (unsigned long long)hash * prime;
+  hash = (product ^ (product >> 24)) & 0xffffff;
 
   // Also add in the additional_number, times some prime factor.  hash = (hash
   // + additional_number * 1657) & 0xffffff;
@@ -690,10 +689,9 @@ hash_string(const string &name, int shift_offset) {
   // deal, since we have to resolve hash conflicts anyway.
 
   string result;
-  int extract_h = hash;
   for (int i = 0; i < 4; i++) {
-    int value = (extract_h & 0x3f);
-    extract_h >>= 6;
+    unsigned int value = (hash & 0x3f);
+    hash >>= 6;
     if (value < 26) {
       result += (char)('A' + value);
 

+ 9 - 1
dtool/src/interrogatedb/py_compat.h

@@ -31,6 +31,14 @@
 
 #include <Python.h>
 
+#ifndef LINK_ALL_STATIC
+#  define EXPCL_PYPANDA
+#elif defined(__GNUC__)
+#  define EXPCL_PYPANDA __attribute__((weak))
+#else
+#  define EXPCL_PYPANDA extern inline
+#endif
+
 /* Python 2.4 */
 
 // 2.4 macros which aren't available in 2.3
@@ -99,7 +107,7 @@ typedef int Py_ssize_t;
 // PyInt_FromSize_t automatically picks the right type.
 #  define PyLongOrInt_AS_LONG PyInt_AsLong
 
-size_t PyLongOrInt_AsSize_t(PyObject *);
+EXPCL_PYPANDA size_t PyLongOrInt_AsSize_t(PyObject *);
 #endif
 
 // Which character to use in PyArg_ParseTuple et al for a byte string.

+ 57 - 4
dtool/src/interrogatedb/py_panda.cxx

@@ -286,6 +286,43 @@ PyObject *_Dtool_Return(PyObject *value) {
 }
 
 #if PY_VERSION_HEX < 0x03040000
+/**
+ * This function converts an int value to the appropriate enum instance.
+ */
+PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) {
+  PyObject *arg;
+  if (!Dtool_ExtractArg(&arg, args, kwds, "value")) {
+    return PyErr_Format(PyExc_TypeError,
+                        "%s() missing 1 required argument: 'value'",
+                        subtype->tp_name);
+  }
+
+  if (Py_TYPE(arg) == subtype) {
+    Py_INCREF(arg);
+    return arg;
+  }
+
+  PyObject *value2member = PyDict_GetItemString(subtype->tp_dict, "_value2member_map_");
+  nassertr_always(value2member != nullptr, nullptr);
+
+  PyObject *member = PyDict_GetItem(value2member, arg);
+  if (member != nullptr) {
+    Py_INCREF(member);
+    return member;
+  }
+
+  PyObject *repr = PyObject_Repr(arg);
+  PyErr_Format(PyExc_ValueError, "%s is not a valid %s",
+#if PY_MAJOR_VERSION >= 3
+               PyUnicode_AS_STRING(repr),
+#else
+               PyString_AS_STRING(repr),
+#endif
+               subtype->tp_name);
+  Py_DECREF(repr);
+  return nullptr;
+}
+
 static PyObject *Dtool_EnumType_Str(PyObject *self) {
   PyObject *name = PyObject_GetAttrString(self, "name");
 #if PY_MAJOR_VERSION >= 3
@@ -337,6 +374,7 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha
   static PyObject *name_sunder_str;
   static PyObject *value_str;
   static PyObject *value_sunder_str;
+  static PyObject *value2member_map_sunder_str;
   // Emulate something vaguely like the enum module.
   if (enum_class == nullptr) {
 #if PY_MAJOR_VERSION >= 3
@@ -344,11 +382,13 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha
     value_str = PyUnicode_InternFromString("value");
     name_sunder_str = PyUnicode_InternFromString("_name_");
     value_sunder_str = PyUnicode_InternFromString("_value_");
+    value2member_map_sunder_str = PyUnicode_InternFromString("_value2member_map_");
 #else
     name_str = PyString_InternFromString("name");
     value_str = PyString_InternFromString("value");
     name_sunder_str = PyString_InternFromString("_name_");
     value_sunder_str = PyString_InternFromString("_value_");
+    value2member_map_sunder_str = PyString_InternFromString("_value2member_map_");
 #endif
     PyObject *name_value_tuple = PyTuple_New(4);
     PyTuple_SET_ITEM(name_value_tuple, 0, name_str);
@@ -365,27 +405,39 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha
     enum_class = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s()N", "Enum", slots_dict);
     nassertr(enum_class != nullptr, nullptr);
   }
-  PyObject *result = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s(O)N", name, enum_class, PyDict_New());
+
+  // Create a subclass of this generic Enum class we just created.
+  PyObject *value2member = PyDict_New();
+  PyObject *dict = PyDict_New();
+  PyDict_SetItem(dict, value2member_map_sunder_str, value2member);
+  PyObject *result = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s(O)N", name, enum_class, dict);
   nassertr(result != nullptr, nullptr);
 
+  ((PyTypeObject *)result)->tp_new = Dtool_EnumType_New;
   ((PyTypeObject *)result)->tp_str = Dtool_EnumType_Str;
   ((PyTypeObject *)result)->tp_repr = Dtool_EnumType_Repr;
 
-  // Copy the names as instances of the above to the class dict.
+  PyObject *empty_tuple = PyTuple_New(0);
+
+  // Copy the names as instances of the above to the class dict, and create a
+  // reverse mapping in the _value2member_map_ dict.
   Py_ssize_t size = PyTuple_GET_SIZE(names);
   for (Py_ssize_t i = 0; i < size; ++i) {
     PyObject *item = PyTuple_GET_ITEM(names, i);
     PyObject *name = PyTuple_GET_ITEM(item, 0);
     PyObject *value = PyTuple_GET_ITEM(item, 1);
-    PyObject *member = _PyObject_CallNoArg(result);
+    PyObject *member = PyType_GenericNew((PyTypeObject *)result, empty_tuple, nullptr);
     PyObject_SetAttr(member, name_str, name);
     PyObject_SetAttr(member, name_sunder_str, name);
     PyObject_SetAttr(member, value_str, value);
     PyObject_SetAttr(member, value_sunder_str, value);
     PyObject_SetAttr(result, name, member);
+    PyDict_SetItem(value2member, value, member);
     Py_DECREF(member);
   }
   Py_DECREF(names);
+  Py_DECREF(value2member);
+  Py_DECREF(empty_tuple);
 #endif
 
   if (module != nullptr) {
@@ -665,7 +717,8 @@ PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) {
 
 // We do expose a dictionay for dtool classes .. this should be removed at
 // some point..
-PyObject *Dtool_AddToDictionary(PyObject *self1, PyObject *args) {
+EXPCL_PYPANDA PyObject *
+Dtool_AddToDictionary(PyObject *self1, PyObject *args) {
   PyObject *self;
   PyObject *subject;
   PyObject *key;

+ 34 - 34
dtool/src/interrogatedb/py_panda.h

@@ -179,19 +179,19 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\
 
 typedef std::map<std::string, Dtool_PyTypedObject *> Dtool_TypeMap;
 
-Dtool_TypeMap *Dtool_GetGlobalTypeMap();
+EXPCL_PYPANDA Dtool_TypeMap *Dtool_GetGlobalTypeMap();
 
 /**
 
  */
-void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer);
+EXPCL_PYPANDA void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer);
 
-void *DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef, int param, const std::string &function_name, bool const_ok, bool report_errors);
+EXPCL_PYPANDA void *DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef, int param, const std::string &function_name, bool const_ok, bool report_errors);
 
-bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer);
+EXPCL_PYPANDA bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer);
 
-bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
-                                            void **answer, const char *method_name);
+EXPCL_PYPANDA bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
+                                                          void **answer, const char *method_name);
 
 template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into);
 template<class T> INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &classdef);
@@ -201,7 +201,7 @@ INLINE int DtoolInstance_ComparePointers(PyObject *v1, PyObject *v2);
 INLINE PyObject *DtoolInstance_RichComparePointers(PyObject *v1, PyObject *v2, int op);
 
 // Functions related to error reporting.
-bool _Dtool_CheckErrorOccurred();
+EXPCL_PYPANDA bool _Dtool_CheckErrorOccurred();
 
 #ifdef NDEBUG
 #define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr))
@@ -209,12 +209,12 @@ bool _Dtool_CheckErrorOccurred();
 #define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred()))
 #endif
 
-PyObject *Dtool_Raise_AssertionError();
-PyObject *Dtool_Raise_TypeError(const char *message);
-PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name);
-PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute);
+EXPCL_PYPANDA PyObject *Dtool_Raise_AssertionError();
+EXPCL_PYPANDA PyObject *Dtool_Raise_TypeError(const char *message);
+EXPCL_PYPANDA PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name);
+EXPCL_PYPANDA PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute);
 
-PyObject *_Dtool_Raise_BadArgumentsError();
+EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError();
 #ifdef NDEBUG
 // Define it to a function that just prints a generic message.
 #define Dtool_Raise_BadArgumentsError(x) _Dtool_Raise_BadArgumentsError()
@@ -226,9 +226,9 @@ PyObject *_Dtool_Raise_BadArgumentsError();
 // These functions are similar to Dtool_WrapValue, except that they also
 // contain code for checking assertions and exceptions when compiling with
 // NDEBUG mode on.
-PyObject *_Dtool_Return_None();
-PyObject *Dtool_Return_Bool(bool value);
-PyObject *_Dtool_Return(PyObject *value);
+EXPCL_PYPANDA PyObject *_Dtool_Return_None();
+EXPCL_PYPANDA PyObject *Dtool_Return_Bool(bool value);
+EXPCL_PYPANDA PyObject *_Dtool_Return(PyObject *value);
 
 #ifdef NDEBUG
 #define Dtool_Return_None() (LIKELY(_PyErr_OCCURRED() == nullptr) ? (Py_INCREF(Py_None), Py_None) : nullptr)
@@ -241,19 +241,19 @@ PyObject *_Dtool_Return(PyObject *value);
 /**
  * Wrapper around Python 3.4's enum library, which does not have a C API.
  */
-PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names,
-                                                        const char *module = nullptr);
+EXPCL_PYPANDA PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names,
+                                                  const char *module = nullptr);
 INLINE long Dtool_EnumValue_AsLong(PyObject *value);
 
 
 /**
 
  */
-PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &known_class_type, bool memory_rules, bool is_const, int RunTimeType);
+EXPCL_PYPANDA PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &known_class_type, bool memory_rules, bool is_const, int RunTimeType);
 
 // DTool_CreatePyInstance .. wrapper function to finalize the existance of a
 // general dtool py instance..
-PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_classdef, bool memory_rules, bool is_const);
+EXPCL_PYPANDA PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_classdef, bool memory_rules, bool is_const);
 
 // These template methods allow use when the Dtool_PyTypedObject is not known.
 // They require a get_class_type() to be defined for the class.
@@ -320,26 +320,26 @@ struct LibraryDef {
 };
 
 #if PY_MAJOR_VERSION >= 3
-PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def);
+EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def);
 #else
-PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename);
+EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename);
 #endif
 
 // HACK.... Be carefull Dtool_BorrowThisReference This function can be used to
 // grab the "THIS" pointer from an object and use it Required to support fom
 // historical inharatence in the for of "is this instance of"..
-PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args);
+EXPCL_PYPANDA PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args);
 
 #define DTOOL_PyObject_HashPointer DtoolInstance_HashPointer
 #define DTOOL_PyObject_ComparePointers DtoolInstance_ComparePointers
 
-PyObject *
+EXPCL_PYPANDA PyObject *
 copy_from_make_copy(PyObject *self, PyObject *noargs);
 
-PyObject *
+EXPCL_PYPANDA PyObject *
 copy_from_copy_constructor(PyObject *self, PyObject *noargs);
 
-PyObject *
+EXPCL_PYPANDA PyObject *
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 
 /**
@@ -348,14 +348,14 @@ map_deepcopy_to_copy(PyObject *self, PyObject *args);
  */
 ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args);
 ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args, PyObject *kwds);
-bool Dtool_ExtractArg(PyObject **result, PyObject *args,
-                                          PyObject *kwds, const char *keyword);
-bool Dtool_ExtractArg(PyObject **result, PyObject *args,
-                                          PyObject *kwds);
-bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args,
-                                                  PyObject *kwds, const char *keyword);
-bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args,
-                                                  PyObject *kwds);
+EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args,
+                                    PyObject *kwds, const char *keyword);
+EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args,
+                                    PyObject *kwds);
+EXPCL_PYPANDA bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args,
+                                            PyObject *kwds, const char *keyword);
+EXPCL_PYPANDA  bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args,
+                                             PyObject *kwds);
 
 /**
  * These functions convert a C++ value into the corresponding Python object.
@@ -390,7 +390,7 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(Py_buffer *value);
 template<class T1, class T2>
 ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::pair<T1, T2> &value);
 
-Dtool_PyTypedObject *Dtool_GetSuperBase();
+EXPCL_PYPANDA Dtool_PyTypedObject *Dtool_GetSuperBase();
 
 #include "py_panda.I"
 

+ 6 - 6
dtool/src/interrogatedb/py_wrappers.h

@@ -49,12 +49,12 @@ struct Dtool_GeneratorWrapper {
   iternextfunc _iternext_func;
 };
 
-Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name);
-Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name);
-Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name);
-Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name);
-PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func);
-PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
+EXPCL_PYPANDA Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name);
+EXPCL_PYPANDA Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name);
+EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name);
+EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name);
+EXPCL_PYPANDA PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func);
+EXPCL_PYPANDA PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
 
 #endif  // HAVE_PYTHON
 

+ 6 - 3
makepanda/makepanda.py

@@ -808,9 +808,12 @@ if (COMPILER == "MSVC"):
                 path = GetThirdpartyDir() + "vorbis/lib/{0}.lib".format(lib)
             LibName("VORBIS", path)
     if (PkgSkip("OPUS")==0):
-        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libogg_static.lib")
-        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopus_static.lib")
-        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopusfile_static.lib")
+        IncDirectory("OPUS", GetThirdpartyDir() + "opus/include/opus")
+        for lib in ('ogg', 'opus', 'opusfile'):
+            path = GetThirdpartyDir() + "opus/lib/lib{0}_static.lib".format(lib)
+            if not os.path.isfile(path):
+                path = GetThirdpartyDir() + "opus/lib/{0}.lib".format(lib)
+            LibName("OPUS", path)
     for pkg in MAYAVERSIONS:
         if (PkgSkip(pkg)==0):
             LibName(pkg, '"' + SDK[pkg] + '/lib/Foundation.lib"')

+ 7 - 2
makepanda/makepandacore.py

@@ -3362,8 +3362,13 @@ def CalcLocation(fn, ipath):
 
 
 def FindLocation(fn, ipath, pyabi=None):
-    if (GetLinkAllStatic() and fn.endswith(".dll")):
-        fn = fn[:-4] + ".lib"
+    if GetLinkAllStatic():
+        if fn.endswith(".dll"):
+            fn = fn[:-4] + ".lib"
+        elif fn.endswith(".pyd"):
+            fn = "libpy.panda3d." \
+               + os.path.splitext(fn[:-4] + GetExtensionSuffix())[0] + ".lib"
+
     loc = CalcLocation(fn, ipath)
     base, ext = os.path.splitext(fn)
 

+ 16 - 16
panda/src/androiddisplay/androidGraphicsWindow.cxx

@@ -55,9 +55,9 @@ AndroidGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
 
   _app = panda_android_app;
 
-  GraphicsWindowInputDevice device =
-    GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
+  PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
   add_input_device(device);
+  _input = device;
 }
 
 /**
@@ -486,22 +486,22 @@ handle_key_event(const AInputEvent *event) {
   /*
   int32_t meta = AKeyEvent_getMetaState(event);
   if (meta | AMETA_ALT_ON) {
-    _input_devices[0].button_down(KeyboardButton.alt());
+    _input->button_down(KeyboardButton.alt());
   }
   if (meta | AMETA_ALT_LEFT_ON) {
-    _input_devices[0].button_down(KeyboardButton.lalt());
+    _input->button_down(KeyboardButton.lalt());
   }
   if (meta | AMETA_ALT_RIGHT_ON) {
-    _input_devices[0].button_down(KeyboardButton.ralt());
+    _input->button_down(KeyboardButton.ralt());
   }
   if (meta | AMETA_SHIFT_ON) {
-    _input_devices[0].button_down(KeyboardButton.shift());
+    _input->button_down(KeyboardButton.shift());
   }
   if (meta | AMETA_SHIFT_LEFT_ON) {
-    _input_devices[0].button_down(KeyboardButton.lshift());
+    _input->button_down(KeyboardButton.lshift());
   }
   if (meta | AMETA_SHIFT_RIGHT_ON) {
-    _input_devices[0].button_down(KeyboardButton.rshift());
+    _input->button_down(KeyboardButton.rshift());
   }*/
 
   int32_t keycode = AKeyEvent_getKeyCode(event);
@@ -517,12 +517,12 @@ handle_key_event(const AInputEvent *event) {
   int32_t action = AKeyEvent_getAction(event);
   if (action == AKEY_EVENT_ACTION_DOWN) {
     if (AKeyEvent_getRepeatCount(event) > 0) {
-      _input_devices[0].button_resume_down(button);
+      _input->button_resume_down(button);
     } else {
-      _input_devices[0].button_down(button);
+      _input->button_down(button);
     }
   } else if (action == AKEY_EVENT_ACTION_UP) {
-    _input_devices[0].button_up(button);
+    _input->button_up(button);
   }
   // TODO AKEY_EVENT_ACTION_MULTIPLE
 
@@ -549,16 +549,16 @@ handle_motion_event(const AInputEvent *event) {
     if (changed != 0) {
       if (changed & AMOTION_EVENT_BUTTON_PRIMARY) {
         if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
-          _input_devices[0].button_down(MouseButton::one());
+          _input->button_down(MouseButton::one());
         } else {
-          _input_devices[0].button_up(MouseButton::one());
+          _input->button_up(MouseButton::one());
         }
       }
       if (changed & AMOTION_EVENT_BUTTON_SECONDARY) {
         if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
-          _input_devices[0].button_down(MouseButton::three());
+          _input->button_down(MouseButton::three());
         } else {
-          _input_devices[0].button_up(MouseButton::three());
+          _input->button_up(MouseButton::three());
         }
       }
       _mouse_button_state = button_state;
@@ -568,7 +568,7 @@ handle_motion_event(const AInputEvent *event) {
   float x = AMotionEvent_getX(event, 0) - _app->contentRect.left;
   float y = AMotionEvent_getY(event, 0) - _app->contentRect.top;
 
-  _input_devices[0].set_pointer_in_window(x, y);
+  _input->set_pointer_in_window(x, y);
 
   return 1;
 }

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

@@ -73,7 +73,7 @@ private:
 
   int32_t _mouse_button_state;
 
-  const ARect *rect;
+  GraphicsWindowInputDevice *_input;
 
 public:
   static TypeHandle get_class_type() {

+ 78 - 5
panda/src/device/evdevInputDevice.cxx

@@ -57,6 +57,11 @@ enum QuirkBits {
 
   // ABS_THROTTLE maps to rudder
   QB_rudder_from_throttle = 16,
+
+  // Special handling for Steam Controller, which has many peculiarities.
+  // We only connect it if it is reporting any events, because when Steam is
+  // running, the Steam controller is muted in favour of a dummy Xbox device.
+  QB_steam_controller = 32,
 };
 
 static const struct DeviceMapping {
@@ -71,8 +76,14 @@ static const struct DeviceMapping {
   {0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle},
   // Xbox 360 Wireless Controller
   {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero},
+  // Steam Controller (wired)
+  {0x28de, 0x1102, InputDevice::DeviceClass::unknown, QB_steam_controller},
+  // Steam Controller (wireless)
+  {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller},
   // Jess Tech Colour Rumble Pad
   {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0},
+  // Trust GXT 24
+  {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0},
   // 3Dconnexion Space Traveller 3D Mouse
   {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0},
   // 3Dconnexion Space Pilot 3D Mouse
@@ -110,7 +121,8 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) :
   _dpad_left_button(-1),
   _dpad_up_button(-1),
   _ltrigger_code(-1),
-  _rtrigger_code(-1) {
+  _rtrigger_code(-1),
+  _quirks(0) {
 
   char path[64];
   sprintf(path, "/dev/input/event%zd", index);
@@ -208,6 +220,26 @@ do_set_vibration(double strong, double weak) {
   }
 }
 
+/**
+ * Special case for Steam controllers; called if a Steam virtual device has
+ * just been disconnected, and this is currently an inactive Steam Controller
+ * previously blocked by Steam, waiting to be reactivated.
+ * Returns true if the device has just been reconnected.
+ */
+bool EvdevInputDevice::
+reactivate_steam_controller() {
+  LightMutexHolder holder(_lock);
+  if (!_is_connected && (_quirks & QB_steam_controller) != 0) {
+    // Just check to make sure the device is still readable.
+    process_events();
+    if (_fd != -1) {
+      _is_connected = true;
+      return true;
+    }
+  }
+  return false;
+}
+
 /**
  * Polls the input device for new activity, to ensure it contains the latest
  * events.  This will only have any effect for some types of input devices;
@@ -219,7 +251,7 @@ do_poll() {
     while (process_events()) {}
 
     // If we got events, we are obviously connected.  Mark us so.
-    if (!_is_connected) {
+    if (!_is_connected && _fd != -1) {
       _is_connected = true;
       if (_manager != nullptr) {
         _manager->add_device(this);
@@ -297,6 +329,24 @@ init_device() {
     ++mapping;
   }
 
+  // The Steam Controller reports as multiple devices, one of which a gamepad.
+  if (quirks & QB_steam_controller) {
+    if (test_bit(BTN_GAMEPAD, keys)) {
+      _device_class = DeviceClass::gamepad;
+
+      // If we have a virtual gamepad on the system, then careful: if Steam is
+      // running, it may disable its own gamepad in favour of the virtual
+      // device it registers.  If the virtual device is present, we will only
+      // register this gamepad as connected when it registers input.
+      if (_manager->has_virtual_device(0x28de, 0x11ff)) {
+        device_cat.debug()
+          << "Detected Steam virtual gamepad, disabling Steam Controller\n";
+        quirks |= QB_connect_if_nonzero;
+      }
+    }
+  }
+  _quirks = quirks;
+
   // Try to detect which type of device we have here
   if (_device_class == DeviceClass::unknown) {
     int device_scores[(size_t)DeviceClass::spatial_mouse] = {0};
@@ -376,7 +426,7 @@ init_device() {
     for (int i = 0; i <= KEY_MAX; ++i) {
       if (test_bit(i, keys)) {
         ButtonState button;
-        button.handle = map_button(i, _device_class);
+        button.handle = map_button(i, _device_class, quirks);
 
         int button_index = (int)_buttons.size();
         if (button.handle == ButtonHandle::none()) {
@@ -525,6 +575,18 @@ init_device() {
             }
           }
           break;
+        case ABS_HAT2X:
+          if (quirks & QB_steam_controller) {
+            axis = InputDevice::Axis::right_trigger;
+            have_analog_triggers = true;
+          }
+          break;
+        case ABS_HAT2Y:
+          if (quirks & QB_steam_controller) {
+            axis = InputDevice::Axis::left_trigger;
+            have_analog_triggers = true;
+          }
+          break;
         }
 
         // Check the initial value and ranges.
@@ -738,7 +800,7 @@ process_events() {
  * Static function to map an evdev code to a ButtonHandle.
  */
 ButtonHandle EvdevInputDevice::
-map_button(int code, DeviceClass device_class) {
+map_button(int code, DeviceClass device_class, int quirks) {
   if (code >= 0 && code < 0x80) {
     // See linux/input.h for the source of this mapping.
     static const ButtonHandle keyboard_map[] = {
@@ -895,7 +957,11 @@ map_button(int code, DeviceClass device_class) {
     }
 
   } else if ((code & 0xfff0) == BTN_JOYSTICK) {
-    if (device_class == DeviceClass::gamepad) {
+    if (quirks & QB_steam_controller) {
+      // BTN_THUMB and BTN_THUMB2 detect touching the touchpads.
+      return ButtonHandle::none();
+
+    } else if (device_class == DeviceClass::gamepad) {
       // Based on "Jess Tech Colour Rumble Pad"
       static const ButtonHandle mapping[] = {
         GamepadButton::face_x(),
@@ -989,6 +1055,13 @@ map_button(int code, DeviceClass device_class) {
   case BTN_TRIGGER_HAPPY4:
     return GamepadButton::dpad_down();
 
+  // The next two are for the Steam Controller's grip buttons.
+  case BTN_GEAR_DOWN:
+    return GamepadButton::lgrip();
+
+  case BTN_GEAR_UP:
+    return GamepadButton::rgrip();
+
   default:
     return ButtonHandle::none();
   }

+ 10 - 1
panda/src/device/evdevInputDevice.h

@@ -30,6 +30,8 @@ public:
   EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index);
   virtual ~EvdevInputDevice();
 
+  bool reactivate_steam_controller();
+
 private:
   virtual void do_set_vibration(double strong, double weak);
   virtual void do_poll();
@@ -41,6 +43,7 @@ private:
   LinuxInputDeviceManager *_manager;
 
   int _fd;
+  int _quirks;
   size_t _index;
 
   bool _can_write;
@@ -64,7 +67,9 @@ private:
   int _rtrigger_code;
 
 public:
-  static ButtonHandle map_button(int code, DeviceClass device_class = DeviceClass::unknown);
+  static ButtonHandle map_button(int code,
+                                 DeviceClass device_class = DeviceClass::unknown,
+                                 int quirks = 0);
 
 public:
   static TypeHandle get_class_type() {
@@ -75,6 +80,10 @@ public:
     register_type(_type_handle, "EvdevInputDevice",
                   InputDevice::get_class_type());
   }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
 
 private:
   static TypeHandle _type_handle;

+ 75 - 0
panda/src/device/linuxInputDeviceManager.cxx

@@ -200,6 +200,55 @@ consider_add_js_device(size_t js_index) {
   return nullptr;
 }
 
+/**
+ * Scans the "virtual" input devices on the system to check whether one with
+ * the given vendor and product ID exists.
+ */
+bool LinuxInputDeviceManager::
+has_virtual_device(unsigned short vendor_id, unsigned short product_id) const {
+  char path[294];
+  sprintf(path, "/sys/devices/virtual/input");
+
+  DIR *dir = opendir(path);
+  if (dir != nullptr) {
+    dirent *entry = readdir(dir);
+    while (entry != nullptr) {
+      if (entry->d_name[0] != 'i') {
+        entry = readdir(dir);
+        continue;
+      }
+      FILE *f;
+
+      char vendor[5] = {0};
+      sprintf(path, "/sys/devices/virtual/input/%s/id/vendor", entry->d_name);
+      f = fopen(path, "r");
+      if (f) {
+        fgets(vendor, sizeof(vendor), f);
+        fclose(f);
+      }
+
+      char product[5] = {0};
+      sprintf(path, "/sys/devices/virtual/input/%s/id/product", entry->d_name);
+      f = fopen(path, "r");
+      if (f) {
+        fgets(product, sizeof(product), f);
+        fclose(f);
+      }
+
+      if (vendor[0] && std::stoi(std::string(vendor), nullptr, 16) == (int)vendor_id &&
+          product[0] && std::stoi(std::string(product), nullptr, 16) == (int)product_id) {
+        closedir(dir);
+        return true;
+      }
+
+      entry = readdir(dir);
+    }
+    closedir(dir);
+  }
+
+  return false;
+}
+
 /**
  * Polls the system to see if there are any new devices.  In some
  * implementations this is a no-op.
@@ -243,6 +292,7 @@ update() {
   LightMutexHolder holder(_lock);
 
   // Iterate over the events in the buffer.
+  bool removed_steam_virtual_device = false;
   char *ptr = buffer;
   char *end = buffer + avail;
   while (ptr < end) {
@@ -270,6 +320,12 @@ update() {
               device_cat.debug()
                 << "Removed input device " << *device << "\n";
             }
+
+            // Check for Steam virtual device; see comment below.
+            if (device->get_vendor_id() == 0x28de &&
+                device->get_product_id() == 0x11ff) {
+              removed_steam_virtual_device = true;
+            }
           }
         }
       }
@@ -290,6 +346,25 @@ update() {
 
     ptr += sizeof(inotify_event) + event->len;
   }
+
+  // If the Steam virtual device was just disconnected, the user may have just
+  // shut down Steam, and we need to reactivate the real Steam Controller
+  // device that was previously suppressed by Steam.
+  if (removed_steam_virtual_device) {
+    inactive_devices = _inactive_devices;
+
+    for (size_t i = 0; i < inactive_devices.size(); ++i) {
+      InputDevice *device = inactive_devices[i];
+      if (device != nullptr && device->is_of_type(EvdevInputDevice::get_class_type())) {
+        PT(EvdevInputDevice) evdev_device = (EvdevInputDevice *)device;
+        if (evdev_device->reactivate_steam_controller()) {
+          _inactive_devices.remove_device(device);
+          _connected_devices.add_device(device);
+          throw_event("connect-device", device);
+        }
+      }
+    }
+  }
 }
 
 #endif  // PHAVE_LINUX_INPUT_H

+ 3 - 0
panda/src/device/linuxInputDeviceManager.h

@@ -30,6 +30,9 @@ private:
   InputDevice *consider_add_evdev_device(size_t index);
   InputDevice *consider_add_js_device(size_t index);
 
+public:
+  bool has_virtual_device(unsigned short vendor_id, unsigned short product_id) const;
+
   virtual void update();
 
 protected:

+ 20 - 5
panda/src/device/linuxJoystickDevice.cxx

@@ -113,6 +113,7 @@ open_device() {
   ioctl(_fd, JSIOCGNAME(sizeof(name)), name);
   _name = name;
 
+  bool emulate_dpad = true;
   bool have_analog_triggers = false;
 
   // Get the number of axes.
@@ -138,6 +139,8 @@ open_device() {
         _device_class = DeviceClass::gamepad;
       } else if (handle == GamepadButton::trigger()) {
         _device_class = DeviceClass::flight_stick;
+      } else if (handle == GamepadButton::dpad_left()) {
+        emulate_dpad = false;
       } else if (handle == GamepadButton::ltrigger()) {
         _ltrigger_button = i;
       } else if (handle == GamepadButton::rtrigger()) {
@@ -220,7 +223,7 @@ open_device() {
         break;
 
       case ABS_HAT0X:
-        if (_dpad_left_button == -1) {
+        if (emulate_dpad) {
           // Emulate D-Pad or hat switch.
           _dpad_x_axis = i;
           _dpad_left_button = (int)_buttons.size();
@@ -236,7 +239,7 @@ open_device() {
         break;
 
       case ABS_HAT0Y:
-        if (_dpad_up_button == -1) {
+        if (emulate_dpad) {
           // Emulate D-Pad.
           _dpad_y_axis = i;
           _dpad_up_button = (int)_buttons.size();
@@ -251,6 +254,18 @@ open_device() {
         }
         break;
 
+      case ABS_HAT2X:
+        if (_device_class == DeviceClass::gamepad) {
+          axis = InputDevice::Axis::right_trigger;
+        }
+        break;
+
+      case ABS_HAT2Y:
+        if (_device_class == DeviceClass::gamepad) {
+          axis = InputDevice::Axis::left_trigger;
+        }
+        break;
+
       default:
         if (device_cat.is_debug()) {
           device_cat.debug() << "Unmapped /dev/input/js" << _index
@@ -278,7 +293,7 @@ open_device() {
 
   if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) {
     // Emulate analog triggers.
-    _ltrigger_control = (int)_axes.size();
+    _ltrigger_axis = (int)_axes.size();
     add_axis(Axis::left_trigger, 0, 1, false);
     add_axis(Axis::right_trigger, 0, 1, false);
   } else {
@@ -398,9 +413,9 @@ process_events() {
 
     if (events[i].type & JS_EVENT_BUTTON) {
       if (index == _ltrigger_button) {
-        axis_changed(_ltrigger_control, events[i].value);
+        axis_changed(_ltrigger_axis, events[i].value);
       } else if (index == _rtrigger_button) {
-        axis_changed(_ltrigger_control + 1, events[i].value);
+        axis_changed(_ltrigger_axis + 1, events[i].value);
       }
       button_changed(index, (events[i].value != 0));
 

+ 1 - 1
panda/src/device/linuxJoystickDevice.h

@@ -50,7 +50,7 @@ private:
   int _dpad_up_button;
 
   // This is used for axis emulation.
-  int _ltrigger_control;
+  int _ltrigger_axis;
   int _ltrigger_button;
   int _rtrigger_button;
 

+ 41 - 1
panda/src/device/winInputDeviceManager.cxx

@@ -82,6 +82,47 @@ WinInputDeviceManager() :
     device_cat.warning()
       << "Failed to register raw input devices.\n";
   }
+
+  // Do we have any XInput devices plugged in now?
+  int num_xinput = 0;
+  HANDLE xinput_handle;
+  RAWINPUTDEVICELIST devices[64];
+  UINT num_devices = 64;
+  num_devices = GetRawInputDeviceList(devices, &num_devices, sizeof(RAWINPUTDEVICELIST));
+  if (num_devices == (UINT)-1) {
+    return;
+  }
+  for (UINT i = 0; i < num_devices; ++i) {
+    if (devices[i].dwType != RIM_TYPEHID) {
+      continue;
+    }
+    HANDLE handle = devices[i].hDevice;
+    UINT size;
+    if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) {
+      continue;
+    }
+
+    char *path = (char *)alloca(size);
+    if (path == nullptr ||
+        GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) {
+      continue;
+    }
+
+    if (strstr(path, "&IG_") != nullptr) {
+      xinput_handle = handle;
+      ++num_xinput;
+    }
+  }
+  if (num_xinput == 1) {
+    // There's only one XInput device, so we know which one it is.
+    on_input_device_arrival(xinput_handle);
+  } else if (num_xinput > 0) {
+    // Just poll all the XInput devices.
+    _xinput_device0.detect(this);
+    _xinput_device1.detect(this);
+    _xinput_device2.detect(this);
+    _xinput_device3.detect(this);
+  }
 }
 
 /**
@@ -277,7 +318,6 @@ on_input_device_arrival(HANDLE handle) {
   if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr) {
     // This is a device we should handle via the XInput API.  Check which of
     // the four players was the lucky one.
-    WinRawInputDevice idev(this, path);
     if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) {
       add_device(&_xinput_device0);
     }

+ 63 - 46
panda/src/device/winRawInputDevice.cxx

@@ -273,6 +273,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
         // Well, it claims to be a gamepad...
         _device_class = DeviceClass::gamepad;
       }
+      //TODO: better solution for this
+      if (_vendor_id == 0x0079 && _product_id == 0x0006) {
+        // Trust GXT 24
+        _device_class = DeviceClass::gamepad;
+      }
 
     // Mice
     } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
@@ -599,13 +604,16 @@ on_removal() {
   _is_connected = false;
   _handle = nullptr;
   if (_preparsed != nullptr) {
-    delete _preparsed;
+    free(_preparsed);
     _preparsed = nullptr;
   }
   _indices.clear();
   _report_buttons.clear();
 }
 
+/**
+ * Called by InputDeviceManager when raw input is received for this device.
+ */
 void WinRawInputDevice::
 on_input(PRAWINPUT input) {
   nassertv(input != nullptr);
@@ -616,59 +624,68 @@ on_input(PRAWINPUT input) {
     return;
   }
 
-  PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
-  nassertv(data != nullptr);
-  ULONG count;
-
   LightMutexHolder holder(_lock);
 
   for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
-    // The first byte is the report identifier.  We need it to figure out
-    // which buttons are off, since each report only contains the buttons that
-    // are "on".
-    UCHAR report_id = ptr[0];
-    BitArray unset_buttons = _report_buttons[report_id];
-
-    count = _max_data_count;
-    NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, (PCHAR)ptr, input->data.hid.dwSizeHid);
-    if (status == HIDP_STATUS_SUCCESS) {
-      for (ULONG di = 0; di < count; ++di) {
-        if (data[di].DataIndex != _hat_data_index) {
-          const Index &idx = _indices[data[di].DataIndex];
-          if (idx._axis >= 0) {
-            if (idx._signed) {
-              axis_changed(idx._axis, (SHORT)data[di].RawValue);
-            } else {
-              axis_changed(idx._axis, data[di].RawValue);
-            }
-          }
-          if (idx._button >= 0) {
-            unset_buttons.clear_bit(idx._button);
-            button_changed(idx._button, (data[di].On != FALSE));
+    process_report((PCHAR)ptr, input->data.hid.dwSizeHid);
+    ptr += input->data.hid.dwSizeHid;
+  }
+}
+
+/**
+ * Processes a single HID report.  Assumes the lock is held.
+ */
+void WinRawInputDevice::
+process_report(PCHAR ptr, size_t size) {
+  // The first byte is the report identifier.  We need it to figure out which
+  // buttons are off, since each report only contains the "on" buttons.
+  UCHAR report_id = ptr[0];
+  BitArray unset_buttons;
+
+  if (report_id < _report_buttons.size()) {
+    unset_buttons = _report_buttons[report_id];
+  }
+
+  PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
+  nassertv(data != nullptr);
+
+  ULONG count = _max_data_count;
+  NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, ptr, size);
+  if (status == HIDP_STATUS_SUCCESS) {
+    for (ULONG di = 0; di < count; ++di) {
+      if (data[di].DataIndex != _hat_data_index) {
+        const Index &idx = _indices[data[di].DataIndex];
+        if (idx._axis >= 0) {
+          if (idx._signed) {
+            axis_changed(idx._axis, (SHORT)data[di].RawValue);
+          } else {
+            axis_changed(idx._axis, data[di].RawValue);
           }
-        } else {
-          int value = (int)data[di].RawValue - _hat_data_minimum;
-          button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
-          button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
-          button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
-          button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
         }
+        if (idx._button >= 0) {
+          unset_buttons.clear_bit(idx._button);
+          button_changed(idx._button, (data[di].On != FALSE));
+        }
+      } else {
+        int value = (int)data[di].RawValue - _hat_data_minimum;
+        button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
+        button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
+        button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
+        button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
       }
-
-      // Now unset the buttons in this report that aren't pressed.
-      int button_index = unset_buttons.get_lowest_on_bit();
-      while (button_index >= 0) {
-        button_changed(button_index, false);
-        unset_buttons.clear_bit(button_index);
-        button_index = unset_buttons.get_lowest_on_bit();
-      }
-    } else if (device_cat.is_spam()) {
-      device_cat.spam()
-        << "Failed to get data from raw device " << _path
-        << " (error 0x" << std::hex << (status & 0xffffffffu) << std::dec << ")\n";
     }
 
-    ptr += input->data.hid.dwSizeHid;
+    // Now unset the buttons in this report that aren't pressed.
+    int button_index = unset_buttons.get_lowest_on_bit();
+    while (button_index >= 0) {
+      button_changed(button_index, false);
+      unset_buttons.clear_bit(button_index);
+      button_index = unset_buttons.get_lowest_on_bit();
+    }
+  } else if (device_cat.is_spam()) {
+    device_cat.spam()
+      << "Failed to get data from raw device " << _path
+      << " (error 0x" << std::hex << (status & 0xffffffffu) << std::dec << ")\n";
   }
 }
 

+ 1 - 0
panda/src/device/winRawInputDevice.h

@@ -34,6 +34,7 @@ public:
   bool on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name);
   void on_removal();
   void on_input(PRAWINPUT input);
+  void process_report(PCHAR ptr, size_t size);
 
 private:
   virtual void do_poll();

+ 12 - 0
panda/src/device/xInputDevice.cxx

@@ -161,6 +161,10 @@ check_arrival(const RID_DEVICE_INFO &info, DEVINST inst,
     return false;
   }
 
+  if (get_state(_index, &state) != ERROR_SUCCESS) {
+    return false;
+  }
+
   // Extra check for VID/PID if we have it, just to be sure.
   if ((caps.VendorID != 0 && caps.VendorID != info.hid.dwVendorId) ||
       (caps.ProductID != 0 && caps.ProductID != info.hid.dwProductId)) {
@@ -205,6 +209,10 @@ check_arrival(const RID_DEVICE_INFO &info, DEVINST inst,
  */
 void XInputDevice::
 detect(InputDeviceManager *mgr) {
+  if (!_initialized) {
+    nassertv_always(init_xinput());
+  }
+
   bool connected = false;
 
   XINPUT_CAPABILITIES_EX caps = {0};
@@ -225,6 +233,10 @@ detect(InputDeviceManager *mgr) {
   _is_connected = connected;
 
   if (connected) {
+    _name = "XInput Device #";
+    _name += format_string(_index + 1);
+    _vendor_id = caps.VendorID;
+    _product_id = caps.ProductID;
     init_device(caps, state);
     mgr->add_device(this);
   } else {

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

@@ -478,15 +478,6 @@ ConfigVariableBool sync_video
           "cheesy estimate of scene complexity.  Some drivers may ignore "
           "this request."));
 
-ConfigVariableBool basic_shaders_only
-("basic-shaders-only", false,
- PRC_DESC("Set this to true if you aren't interested in shader model three "
-          "and beyond.  Setting this flag will cause panda to disable "
-          "bleeding-edge shader functionality which tends to be unreliable "
-          "or broken.  At some point, when functionality that is currently "
-          "flaky becomes reliable, we may expand the definition of what "
-          "constitutes 'basic' shaders."));
-
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be

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

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

+ 2 - 1
panda/src/display/graphicsStateGuardian.cxx

@@ -1255,7 +1255,8 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     return &(_scene_setup->get_camera_transform()->get_mat());
   }
   case Shader::SMO_model_to_view: {
-    return &(_inv_cs_transform->compose(_internal_transform)->get_mat());
+    t = _inv_cs_transform->compose(_internal_transform)->get_mat();
+    return &t;
   }
   case Shader::SMO_model_to_apiview: {
     return &(_internal_transform->get_mat());

+ 5 - 44
panda/src/egg2pg/eggSaver.cxx

@@ -52,7 +52,6 @@
 #include "modelNode.h"
 #include "animBundleNode.h"
 #include "animChannelMatrixXfmTable.h"
-#include "characterJointEffect.h"
 #include "characterJoint.h"
 #include "character.h"
 #include "string_utils.h"
@@ -159,16 +158,6 @@ convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
     convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal);
 
   } else {
-    // Is this a ModelNode that represents an exposed joint?  If so, skip it,
-    // as we'll take care of it when building the joint hierarchy.
-    if (node->get_type() == ModelNode::get_class_type()) {
-      ModelNode *model_node = (ModelNode *)node;
-      if (model_node->get_preserve_transform() == ModelNode::PT_net &&
-          model_node->has_effect(CharacterJointEffect::get_class_type())) {
-        return;
-      }
-    }
-
     // Just a generic node.
     EggGroup *egg_group = new EggGroup(node->get_name());
     egg_parent->add_child(egg_group);
@@ -368,17 +357,6 @@ convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, Charac
     EggGroup *joint = new EggGroup(bundleNode->get_name());
     joint->add_matrix4(transformd);
     joint->set_group_type(EggGroup::GT_joint);
-
-    // Is this joint exposed?
-    NodePathCollection coll = character_joint->get_net_transforms();
-    for (size_t i = 0; i < coll.size(); ++i) {
-      const NodePath &np = coll[i];
-      if (np.get_name() == bundleNode->get_name() && np.node()->is_of_type(ModelNode::get_class_type())) {
-        joint->set_dcs_type(EggGroup::DC_net);
-        break;
-      }
-    }
-
     joint_group = joint;
     egg_parent->add_child(joint_group);
     if (joint_map != nullptr) {
@@ -409,33 +387,16 @@ convert_character_node(Character *node, const WorkingNodePath &node_path,
 
   // A sequence node gets converted to an ordinary EggGroup, we only apply the
   // appropriate switch attributes to turn it into a sequence.
+  // We have to use DT_structured since it is the only mode that preserves the
+  // node hierarchy, including LODNodes and CollisionNodes that may be under
+  // this Character node.
   EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_group->set_dart_type(EggGroup::DT_structured);
   egg_parent->add_child(egg_group);
   apply_node_properties(egg_group, node);
 
   CharacterJointMap joint_map;
-  bool is_structured = false;
-
-  int num_children = node->get_num_children();
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-    convert_node(WorkingNodePath(node_path, child), egg_parent, has_decal, &joint_map);
-
-    TypeHandle type = child->get_type();
-    if (child->get_num_children() > 0 ||
-        (type != GeomNode::get_class_type() && type != ModelNode::get_class_type())) {
-      is_structured = true;
-    }
-  }
-
-  // We have to use DT_structured if it is necessary to preserve any node
-  // hierarchy, such as LODNodes and CollisionNodes that may be under this
-  // Character node.
-  if (is_structured) {
-    egg_group->set_dart_type(EggGroup::DT_structured);
-  } else {
-    egg_group->set_dart_type(EggGroup::DT_default);
-  }
+  recurse_nodes(node_path, egg_group, has_decal, &joint_map);
 
   // turn it into a switch.. egg_group->set_switch_flag(true);
 

+ 0 - 2
panda/src/express/config_express.N

@@ -1,5 +1,3 @@
-forcetype DConfig
-
 forcetype PTA_uchar
 forcetype CPTA_uchar
 forcetype PTA_float

+ 10 - 0
panda/src/glstuff/glCgShaderContext_src.cxx

@@ -157,6 +157,16 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte
           break;
         case 2:  // gl_Normal
           loc = CA_normal;
+          if (cgGetParameterColumns(p) == 4) {
+            // Don't declare vtx_normal with 4 coordinates; it results in it
+            // reading the w coordinate from random memory.
+            GLCAT.error()
+              << "Cg varying " << cgGetParameterName(p);
+            if (cgGetParameterSemantic(p)) {
+              GLCAT.error(false) << " : " << cgGetParameterSemantic(p);
+            }
+            GLCAT.error(false) << " should be declared as float4, not float3!\n";
+          }
           break;
         case 3:  // gl_Color
           loc = CA_color;

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

@@ -1684,6 +1684,8 @@ reset() {
       }
     }
 
+    Shader::set_default_caps(_shader_caps);
+
   } else if (_supports_glsl) {
     // No, but we do support GLSL...
     _shader_caps._active_vprofile = (int)CG_PROFILE_GLSLV;

+ 9 - 0
panda/src/gobj/config_gobj.cxx

@@ -511,6 +511,15 @@ ConfigVariableBool stereo_lens_old_convergence
           "old, incorrect behavior, this may be set to 'true' to switch "
           "back to the old calculation."));
 
+ConfigVariableBool basic_shaders_only
+("basic-shaders-only", false,
+ PRC_DESC("Set this to true if you aren't interested in shader model three "
+          "and beyond.  Setting this flag will cause panda to disable "
+          "bleeding-edge shader functionality which tends to be unreliable "
+          "or broken.  At some point, when functionality that is currently "
+          "flaky becomes reliable, we may expand the definition of what "
+          "constitutes 'basic' shaders."));
+
 ConfigVariableString cg_glsl_version
 ("cg-glsl-version", "",
  PRC_DESC("If this is set, it forces the Cg compiler to generate GLSL "

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

@@ -87,6 +87,7 @@ extern EXPCL_PANDA_GOBJ ConfigVariableDouble async_load_delay;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt lens_geom_segments;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool stereo_lens_old_convergence;
 
+extern EXPCL_PANDA_GOBJ ConfigVariableBool basic_shaders_only;
 extern EXPCL_PANDA_GOBJ ConfigVariableString cg_glsl_version;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool glsl_preprocess;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt glsl_include_recursion_limit;

+ 18 - 3
panda/src/gobj/shader.cxx

@@ -1589,6 +1589,15 @@ get_compiled(unsigned int &format, string &binary) const {
   return !binary.empty();
 }
 
+/**
+ * Called by the graphics back-end to specify the caps with which we will
+ * likely want to be compiling our shaders.
+ */
+void Shader::
+set_default_caps(const ShaderCaps &caps) {
+  _default_caps = caps;
+}
+
 #ifdef HAVE_CG
 /**
  *
@@ -2364,9 +2373,15 @@ Shader(ShaderLanguage lang) :
   _cg_fprofile = CG_PROFILE_UNKNOWN;
   _cg_gprofile = CG_PROFILE_UNKNOWN;
   if (_default_caps._ultimate_vprofile == 0 || _default_caps._ultimate_vprofile == CG_PROFILE_UNKNOWN) {
-    _default_caps._active_vprofile = CG_PROFILE_GENERIC;
-    _default_caps._active_fprofile = CG_PROFILE_GENERIC;
-    _default_caps._active_gprofile = CG_PROFILE_GENERIC;
+    if (basic_shaders_only) {
+      _default_caps._active_vprofile = CG_PROFILE_ARBVP1;
+      _default_caps._active_fprofile = CG_PROFILE_ARBFP1;
+      _default_caps._active_gprofile = CG_PROFILE_UNKNOWN;
+    } else {
+      _default_caps._active_vprofile = CG_PROFILE_UNKNOWN;
+      _default_caps._active_fprofile = CG_PROFILE_UNKNOWN;
+      _default_caps._active_gprofile = CG_PROFILE_UNKNOWN;
+    }
     _default_caps._ultimate_vprofile = cgGetProfile("glslv");
     _default_caps._ultimate_fprofile = cgGetProfile("glslf");
     _default_caps._ultimate_gprofile = cgGetProfile("glslg");

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

@@ -527,6 +527,8 @@ public:
   void set_compiled(unsigned int format, const char *data, size_t length);
   bool get_compiled(unsigned int &format, std::string &binary) const;
 
+  static void set_default_caps(const ShaderCaps &caps);
+
 private:
 #ifdef HAVE_CG
   ShaderArgClass cg_parameter_class(CGparameter p);

+ 3 - 1
panda/src/grutil/shaderTerrainMesh.cxx

@@ -156,7 +156,9 @@ bool ShaderTerrainMesh::generate() {
  *   the chunks, and the PNMImage is destroyed afterwards.
  */
 void ShaderTerrainMesh::do_extract_heightfield() {
-  nassertv(_heightfield_tex->has_ram_image()); // Heightfield not in RAM, extract ram image first
+  if (!_heightfield_tex->has_ram_image()) {
+    _heightfield_tex->reload();
+  }
 
   _heightfield_tex->store(_heightfield);
 

+ 8 - 45
panda/src/pgraphnodes/lightLensNode.I

@@ -28,51 +28,6 @@ is_shadow_caster() const {
   return _shadow_caster;
 }
 
-/**
- * Sets the flag indicating whether this light should cast shadows or not.
- * This is the variant without buffer size, meaning that the current buffer
- * size will be kept (512x512 is the default). Note that enabling shadows will
- * require the shader generator to be enabled on the scene.
- */
-INLINE void LightLensNode::
-set_shadow_caster(bool caster) {
-  if (_shadow_caster && !caster) {
-    clear_shadow_buffers();
-  }
-  _shadow_caster = caster;
-  set_active(caster);
-  if (caster) {
-    setup_shadow_map();
-  }
-}
-
-/**
- * Sets the flag indicating whether this light should cast shadows or not.
- * The xsize and ysize parameters specify the size of the shadow buffer that
- * will be set up, the sort parameter specifies the sort.  Note that enabling
- * shadows will require the shader generator to be enabled on the scene.
- */
-INLINE void LightLensNode::
-set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_sort) {
-  if ((_shadow_caster && !caster) || buffer_xsize != _sb_size[0] || buffer_ysize != _sb_size[1]) {
-    clear_shadow_buffers();
-  }
-  _shadow_caster = caster;
-  _sb_size.set(buffer_xsize, buffer_ysize);
-
-  if (buffer_sort != _sb_sort) {
-    ShadowBuffers::iterator it;
-    for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) {
-      (*it).second->set_sort(buffer_sort);
-    }
-    _sb_sort = buffer_sort;
-  }
-  set_active(caster);
-  if (caster) {
-    setup_shadow_map();
-  }
-}
-
 /**
  * Returns the sort of the shadow buffer to be created for this light source.
  */
@@ -115,3 +70,11 @@ get_shadow_buffer(GraphicsStateGuardianBase *gsg) {
     return (*it).second;
   }
 }
+
+/**
+ * Marks this light as having been used by the auto shader.
+ */
+INLINE void LightLensNode::
+mark_used_by_auto_shader() const {
+  _used_by_auto_shader = true;
+}

+ 58 - 2
panda/src/pgraphnodes/lightLensNode.cxx

@@ -19,6 +19,7 @@
 #include "renderState.h"
 #include "cullFaceAttrib.h"
 #include "colorWriteAttrib.h"
+#include "graphicsStateGuardianBase.h"
 
 TypeHandle LightLensNode::_type_handle;
 
@@ -29,7 +30,8 @@ LightLensNode::
 LightLensNode(const std::string &name, Lens *lens) :
   Camera(name, lens),
   _has_specular_color(false),
-  _attrib_count(0)
+  _attrib_count(0),
+  _used_by_auto_shader(false)
 {
   set_active(false);
   _shadow_caster = false;
@@ -65,13 +67,67 @@ LightLensNode(const LightLensNode &copy) :
   _sb_size(copy._sb_size),
   _sb_sort(-10),
   _has_specular_color(copy._has_specular_color),
-  _attrib_count(0)
+  _attrib_count(0),
+  _used_by_auto_shader(false)
 {
   if (_shadow_caster) {
     setup_shadow_map();
   }
 }
 
+/**
+ * Sets the flag indicating whether this light should cast shadows or not.
+ * This is the variant without buffer size, meaning that the current buffer
+ * size will be kept (512x512 is the default). Note that enabling shadows will
+ * require the shader generator to be enabled on the scene.
+ */
+void LightLensNode::
+set_shadow_caster(bool caster) {
+  if (_shadow_caster && !caster) {
+    clear_shadow_buffers();
+  }
+  if (_shadow_caster != caster && _used_by_auto_shader) {
+    // Make sure any shaders using this light are regenerated.
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
+  _shadow_caster = caster;
+  set_active(caster);
+  if (caster) {
+    setup_shadow_map();
+  }
+}
+
+/**
+ * Sets the flag indicating whether this light should cast shadows or not.
+ * The xsize and ysize parameters specify the size of the shadow buffer that
+ * will be set up, the sort parameter specifies the sort.  Note that enabling
+ * shadows will require the shader generator to be enabled on the scene.
+ */
+void LightLensNode::
+set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_sort) {
+  if ((_shadow_caster && !caster) || buffer_xsize != _sb_size[0] || buffer_ysize != _sb_size[1]) {
+    clear_shadow_buffers();
+  }
+  if (_shadow_caster != caster && _used_by_auto_shader) {
+    // Make sure any shaders using this light are regenerated.
+    GraphicsStateGuardianBase::mark_rehash_generated_shaders();
+  }
+  _shadow_caster = caster;
+  _sb_size.set(buffer_xsize, buffer_ysize);
+
+  if (buffer_sort != _sb_sort) {
+    ShadowBuffers::iterator it;
+    for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) {
+      (*it).second->set_sort(buffer_sort);
+    }
+    _sb_sort = buffer_sort;
+  }
+  set_active(caster);
+  if (caster) {
+    setup_shadow_map();
+  }
+}
+
 /**
  * Clears the shadow buffers, meaning they will be automatically recreated
  * when the Shader Generator needs them.

+ 6 - 2
panda/src/pgraphnodes/lightLensNode.h

@@ -38,8 +38,8 @@ PUBLISHED:
   INLINE bool has_specular_color() const;
 
   INLINE bool is_shadow_caster() const;
-  INLINE void set_shadow_caster(bool caster);
-  INLINE void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10);
+  void set_shadow_caster(bool caster);
+  void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10);
 
   INLINE int get_shadow_buffer_sort() const;
 
@@ -52,6 +52,9 @@ PUBLISHED:
   MAKE_PROPERTY(shadow_caster, is_shadow_caster);
   MAKE_PROPERTY(shadow_buffer_size, get_shadow_buffer_size, set_shadow_buffer_size);
 
+public:
+  INLINE void mark_used_by_auto_shader() const;
+
 protected:
   LightLensNode(const LightLensNode &copy);
   void clear_shadow_buffers();
@@ -61,6 +64,7 @@ protected:
   bool _shadow_caster;
   bool _has_specular_color;
   int _sb_sort;
+  mutable bool _used_by_auto_shader = false;
 
   PT(Texture) _shadow_map;
 

+ 8 - 2
panda/src/pgraphnodes/shaderGenerator.cxx

@@ -294,8 +294,14 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
 
       if (node->is_of_type(LightLensNode::get_class_type())) {
         const LightLensNode *llnode = (const LightLensNode *)node;
-        if (shader_attrib->auto_shadow_on() && llnode->is_shadow_caster()) {
-          info._flags |= ShaderKey::LF_has_shadows;
+        if (shader_attrib->auto_shadow_on()) {
+          if (llnode->is_shadow_caster()) {
+            info._flags |= ShaderKey::LF_has_shadows;
+          }
+
+          // Make sure that the next time the shadows are toggled on this
+          // light, it triggers a state rehash.
+          llnode->mark_used_by_auto_shader();
         }
         if (llnode->has_specular_color()) {
           info._flags |= ShaderKey::LF_has_specular_color;

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

@@ -17,8 +17,7 @@
 #include "pandabase.h"
 #include "mutexImpl.h"
 #include "reMutexDirect.h"
-
-class Thread;
+#include "thread.h"
 
 #ifndef DEBUG_THREADS
 

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

@@ -17,8 +17,7 @@
 #include "pandabase.h"
 #include "mutexTrueImpl.h"
 #include "conditionVarImpl.h"
-
-class Thread;
+#include "thread.h"
 
 #ifndef DEBUG_THREADS
 

+ 4 - 5
panda/src/pnmimagetypes/pnmFileTypePNG.cxx

@@ -338,11 +338,12 @@ read_data(xel *array, xelval *alpha_data) {
   // format, mainly because we keep array and alpha data separately, and there
   // doesn't appear to be good support to get this stuff out row-at-a-time for
   // interlaced files.
-  png_bytep *rows = (png_bytep *)PANDA_MALLOC_ARRAY(num_rows * sizeof(png_bytep));
+  png_bytep *rows = (png_bytep *)alloca(num_rows * sizeof(png_bytep));
   int yi;
 
+  png_byte *alloc = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte) * num_rows);
   for (yi = 0; yi < num_rows; yi++) {
-    rows[yi] = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte));
+    rows[yi] = alloc + (row_byte_length * sizeof(png_byte)) * yi;
   }
 
   png_read_image(_png, rows);
@@ -402,12 +403,10 @@ read_data(xel *array, xelval *alpha_data) {
     }
 
     nassertr(source <= rows[yi] + row_byte_length, yi);
-    PANDA_FREE_ARRAY(rows[yi]);
   }
 
-  PANDA_FREE_ARRAY(rows);
-
   png_read_end(_png, nullptr);
+  PANDA_FREE_ARRAY(alloc);
 
   return _y_size;
 }

+ 4 - 0
panda/src/putil/gamepadButton.cxx

@@ -24,6 +24,8 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(lshoulder)
 DEFINE_GAMEPAD_BUTTON_HANDLE(rshoulder)
 DEFINE_GAMEPAD_BUTTON_HANDLE(ltrigger)
 DEFINE_GAMEPAD_BUTTON_HANDLE(rtrigger)
+DEFINE_GAMEPAD_BUTTON_HANDLE(lgrip)
+DEFINE_GAMEPAD_BUTTON_HANDLE(rgrip)
 
 DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_left)
 DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_right)
@@ -87,6 +89,8 @@ init_gamepad_buttons() {
   ButtonRegistry::ptr()->register_button(_rshoulder, "rshoulder");
   ButtonRegistry::ptr()->register_button(_ltrigger, "ltrigger");
   ButtonRegistry::ptr()->register_button(_rtrigger, "rtrigger");
+  ButtonRegistry::ptr()->register_button(_lgrip, "lgrip");
+  ButtonRegistry::ptr()->register_button(_rgrip, "rgrip");
 
   ButtonRegistry::ptr()->register_button(_dpad_left, "dpad_left");
   ButtonRegistry::ptr()->register_button(_dpad_right, "dpad_right");

+ 2 - 0
panda/src/putil/gamepadButton.h

@@ -30,6 +30,8 @@ PUBLISHED:
   static ButtonHandle rshoulder();
   static ButtonHandle ltrigger();
   static ButtonHandle rtrigger();
+  static ButtonHandle lgrip();
+  static ButtonHandle rgrip();
 
   static ButtonHandle dpad_left();
   static ButtonHandle dpad_right();

+ 1 - 0
panda/src/x11display/x11GraphicsPipe.cxx

@@ -241,6 +241,7 @@ x11GraphicsPipe(const std::string &display) :
   _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
   _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
   _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
+  _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
 }
 
 /**

+ 1 - 0
panda/src/x11display/x11GraphicsPipe.h

@@ -80,6 +80,7 @@ public:
   Atom _net_wm_state_below;
   Atom _net_wm_state_add;
   Atom _net_wm_state_remove;
+  Atom _net_wm_bypass_compositor;
 
   // Extension functions.
   typedef int (*pfn_XcursorGetDefaultSize)(X11_Display *);

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

@@ -1272,6 +1272,14 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) {
                   XA_CARDINAL, 32, PropModeReplace,
                   (unsigned char *)&pid, 1);
 
+  // Disable compositing effects in fullscreen mode.
+  if (properties.has_fullscreen()) {
+    int32_t compositor = properties.get_fullscreen() ? 1 : 0;
+    XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_bypass_compositor,
+                    XA_CARDINAL, 32, PropModeReplace,
+                    (unsigned char *)&compositor, 1);
+  }
+
   XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_window_type,
                   XA_ATOM, 32, PropModeReplace,
                   (unsigned char *)type_data, next_type_data);

+ 8 - 10
samples/cartoon-shader/normalGen.sha

@@ -3,14 +3,14 @@
 //Cg profile arbvp1 arbfp1
 
 void vshader(float4 vtx_position : POSITION,
-             float4 vtx_normal : NORMAL,
-            out float4 l_position : POSITION,
-            out float3 l_color : TEXCOORD0,   
-            uniform float4x4 mat_modelproj,
-            uniform float4x4 itp_modelview)
+             float3 vtx_normal : NORMAL,
+             out float4 l_position : POSITION,
+             out float3 l_color : TEXCOORD0,
+             uniform float4x4 mat_modelproj,
+             uniform float4x4 itp_modelview)
 {
-  l_position=mul(mat_modelproj, vtx_position);
-  l_color=(float3)mul(itp_modelview, vtx_normal);
+  l_position = mul(mat_modelproj, vtx_position);
+  l_color = (float3)mul(itp_modelview, float4(vtx_normal, 0));
 }
 
 void fshader(float3 l_color: TEXCOORD0,
@@ -18,8 +18,6 @@ void fshader(float3 l_color: TEXCOORD0,
 {
   l_color = normalize(l_color);
   l_color = l_color/2;
-  o_color.rgb = l_color + float4(0.5, 0.5, 0.5, 0.5);
+  o_color.rgb = l_color.rgb + float3(0.5, 0.5, 0.5);
   o_color.a = 1;
 }
-
-

+ 2 - 2
samples/fireflies/model.sha

@@ -4,7 +4,7 @@
 
 void vshader(float4 vtx_position : POSITION,
              float2 vtx_texcoord0 : TEXCOORD0,
-             float4 vtx_normal : NORMAL,
+             float3 vtx_normal : NORMAL,
              float4 vtx_color : COLOR,
              out float4 l_position : POSITION,
              out float2 l_texcoord0 : TEXCOORD0,
@@ -16,7 +16,7 @@ void vshader(float4 vtx_position : POSITION,
   l_position=mul(mat_modelproj, vtx_position);
   l_texcoord0 = vtx_texcoord0;
   l_color = vtx_color;
-  l_normal = (float3)mul(itp_modelview, vtx_normal);
+  l_normal = (float3)mul(itp_modelview, float4(vtx_normal, 0));
 }
 
 void fshader(float2 l_texcoord0: TEXCOORD0,

+ 2 - 0
samples/gamepad/device_tester.py

@@ -113,6 +113,8 @@ class DeviceConnectivityMonitor(DirectObject):
         self.devices[self.current_panel].show()
 
     def connect_device(self, device):
+        if device in self.devices:
+            return
         self.devices[device] = DeviceMonitor(device)
         self.switch_to_panel(device)
         self.create_menu_button(device)