From 952031455c06eb10593d729a418d631f23ffb726 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sat, 29 Dec 2018 02:28:47 -0700 Subject: [PATCH 01/23] express: Remove stray reference to DConfig --- panda/src/express/config_express.N | 2 -- 1 file changed, 2 deletions(-) diff --git a/panda/src/express/config_express.N b/panda/src/express/config_express.N index e68b43fbfd..abc2d79e63 100644 --- a/panda/src/express/config_express.N +++ b/panda/src/express/config_express.N @@ -1,5 +1,3 @@ -forcetype DConfig - forcetype PTA_uchar forcetype CPTA_uchar forcetype PTA_float From 818fdbd232f3c53af04ec3d8c3974c0b05a68a78 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 30 Dec 2018 04:20:50 -0700 Subject: [PATCH 02/23] interrogate: Tidy up hash_string function The main motivation behind this change is to get rid of a signed integer overflow that sometimes happens in the prime multiplication step, which is (per the C++ spec) undefined behavior. However, it's probably for the best to use only unsigned int when the function is clearly trying to avoid negative values. Not that I suspect it matters much, but I have also heavily tested that the behavior of the function is unchanged (at least on PC hardware - signed integer overflow doesn't behave portably) although it may now be slightly faster due to the fact that I have removed the floating-point math. --- dtool/src/interrogate/interrogateBuilder.cxx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/dtool/src/interrogate/interrogateBuilder.cxx b/dtool/src/interrogate/interrogateBuilder.cxx index da0e2a5318..7473d085d3 100644 --- a/dtool/src/interrogate/interrogateBuilder.cxx +++ b/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); From 0a43008313424e222e335f910aee58bbe1aa8081 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:42:50 +0100 Subject: [PATCH 03/23] py_panda: avoid duplicate symbol issues with LINK_ALL_STATIC Fixes #478 --- dtool/src/interrogatedb/py_compat.h | 10 +++- dtool/src/interrogatedb/py_panda.cxx | 3 +- dtool/src/interrogatedb/py_panda.h | 68 +++++++++++++-------------- dtool/src/interrogatedb/py_wrappers.h | 12 ++--- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 4872faedc7..11771d1948 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -31,6 +31,14 @@ #include +#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. diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 5c984cec82..7c2bef1ce7 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -665,7 +665,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; diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index ec2765e77a..353af53608 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -179,19 +179,19 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ typedef std::map 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 INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into); template 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 ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::pair &value); -Dtool_PyTypedObject *Dtool_GetSuperBase(); +EXPCL_PYPANDA Dtool_PyTypedObject *Dtool_GetSuperBase(); #include "py_panda.I" diff --git a/dtool/src/interrogatedb/py_wrappers.h b/dtool/src/interrogatedb/py_wrappers.h index 96375d3598..eb5658cb6b 100644 --- a/dtool/src/interrogatedb/py_wrappers.h +++ b/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 From cedd4172c4e517eb40d7d95055056477663bf92e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:45:27 +0100 Subject: [PATCH 04/23] makepanda: build Python libraries statically with --static Fixes #477 --- makepanda/makepandacore.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index f84ac24316..8790538395 100644 --- a/makepanda/makepandacore.py +++ b/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) From 73083127bf546ad7169390c2cf932dbbe915a189 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:59:32 +0100 Subject: [PATCH 05/23] x11display: bypass compositor when switching to fullscreen mode --- panda/src/x11display/x11GraphicsPipe.cxx | 1 + panda/src/x11display/x11GraphicsPipe.h | 1 + panda/src/x11display/x11GraphicsWindow.cxx | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index 479b3fde0c..e5e8da18c8 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/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); } /** diff --git a/panda/src/x11display/x11GraphicsPipe.h b/panda/src/x11display/x11GraphicsPipe.h index c13b192627..3b8802e6e2 100644 --- a/panda/src/x11display/x11GraphicsPipe.h +++ b/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 *); diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index be28a6254f..df038a6dad 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/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); From 62781c154b97ea57a2b39c98d4ca0d28e29ed404 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 20:09:57 +0100 Subject: [PATCH 06/23] makepanda: fix instructions to find Opus thirdparty pkg on Windows --- makepanda/makepanda.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index f4f3da2357..40febe3cd0 100755 --- a/makepanda/makepanda.py +++ b/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"') From 42405dff2edd11bc4e05058cc5befa817c74ebe9 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 20:10:50 +0100 Subject: [PATCH 07/23] pnmimagetypes: fix allocation efficiency in PNG reader Rather than making one allocation per row, it instead makes one allocation for all rows. --- panda/src/pnmimagetypes/pnmFileTypePNG.cxx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx b/panda/src/pnmimagetypes/pnmFileTypePNG.cxx index 799f4d1dce..7d64faed50 100644 --- a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx +++ b/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; } From 4a7266d420622185f4adca335545b247382f6c0a Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:09:39 +0100 Subject: [PATCH 08/23] =?UTF-8?q?samples:=20float4=E2=86=92float3=20for=20?= =?UTF-8?q?vtx=5Fnormal=20in=20cartoon=20&=20fireflies=20samples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It seems that Cg's glslv profile causes the w coordinate of the normal column to be random. Fixes #494 Fixes #495 --- samples/cartoon-shader/normalGen.sha | 18 ++++++++---------- samples/fireflies/model.sha | 4 ++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/samples/cartoon-shader/normalGen.sha b/samples/cartoon-shader/normalGen.sha index a3642b0630..53b994b1ae 100644 --- a/samples/cartoon-shader/normalGen.sha +++ b/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; } - - diff --git a/samples/fireflies/model.sha b/samples/fireflies/model.sha index f1867ee5da..0c2e7fd920 100644 --- a/samples/fireflies/model.sha +++ b/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, From 0cfd86d0f4128296d322293658335c5422501897 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:11:33 +0100 Subject: [PATCH 09/23] glgsg: warn if Cg shader uses float4 vtx_normal See #495; Cg's glslv profile does not handle a float4 vtx_normal well and causes garbage to be in the w coordinate. --- panda/src/glstuff/glCgShaderContext_src.cxx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/panda/src/glstuff/glCgShaderContext_src.cxx b/panda/src/glstuff/glCgShaderContext_src.cxx index 649208fb47..6e7fc565cc 100644 --- a/panda/src/glstuff/glCgShaderContext_src.cxx +++ b/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; From 8119547e9015ce90ab28c3191ec9a1f4b614f9bb Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:12:40 +0100 Subject: [PATCH 10/23] device: add hacky detection of Trust GXT 24 as gamepad for now --- panda/src/device/evdevInputDevice.cxx | 2 ++ panda/src/device/winRawInputDevice.cxx | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index a5c916d8bc..610fb8722a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -73,6 +73,8 @@ static const struct DeviceMapping { {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero}, // 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 diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 1af01cd4b4..f49a7e0d7f 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/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 && From 9382e09b7ca29deec9f299544dfcb32828f364b7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:13:45 +0100 Subject: [PATCH 11/23] display: fix returning reference to temporary in shader input This might cause issues when the transform cache is off. --- panda/src/display/graphicsStateGuardian.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 1b0caeabd0..93a3f36651 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/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()); From 83374de0fffee571849127498bf9dd66d2c352e7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 14:01:26 +0100 Subject: [PATCH 12/23] shader: don't use GENERIC profile to compile Cg shaders Instead, make an educated guess of what the GSG we are going to be compiling this shader for wants to use as profile. --- panda/src/display/config_display.cxx | 9 -------- panda/src/display/config_display.h | 1 - .../glstuff/glGraphicsStateGuardian_src.cxx | 2 ++ panda/src/gobj/config_gobj.cxx | 9 ++++++++ panda/src/gobj/config_gobj.h | 1 + panda/src/gobj/shader.cxx | 21 ++++++++++++++++--- panda/src/gobj/shader.h | 2 ++ 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 78c0b17418..965d68f4e3 100644 --- a/panda/src/display/config_display.cxx +++ b/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 diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index 0b76e06472..21f1acb510 100644 --- a/panda/src/display/config_display.h +++ b/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(); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 1e6d1781cc..6151a2c00d 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/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; diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index 12840f7d04..07075a94a8 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/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 " diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h index 7e769c0f45..90a5673c12 100644 --- a/panda/src/gobj/config_gobj.h +++ b/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; diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index fde084aa18..d42e6a4734 100644 --- a/panda/src/gobj/shader.cxx +++ b/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"); diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h index 35971b243b..940e2d0ccf 100644 --- a/panda/src/gobj/shader.h +++ b/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); From 4699dfcd5b8334e9ada059f92c7fc7b80a5a49a2 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 14:07:14 +0100 Subject: [PATCH 13/23] ShaderGenerator: fix toggling shadows while shader gen is on --- panda/src/pgraphnodes/lightLensNode.I | 53 +++----------------- panda/src/pgraphnodes/lightLensNode.cxx | 60 ++++++++++++++++++++++- panda/src/pgraphnodes/lightLensNode.h | 8 ++- panda/src/pgraphnodes/shaderGenerator.cxx | 10 +++- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/panda/src/pgraphnodes/lightLensNode.I b/panda/src/pgraphnodes/lightLensNode.I index 9c59b6c998..4fd1138b03 100644 --- a/panda/src/pgraphnodes/lightLensNode.I +++ b/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; +} diff --git a/panda/src/pgraphnodes/lightLensNode.cxx b/panda/src/pgraphnodes/lightLensNode.cxx index 87f9a5bca8..aaa290e08b 100644 --- a/panda/src/pgraphnodes/lightLensNode.cxx +++ b/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 ©) : _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. diff --git a/panda/src/pgraphnodes/lightLensNode.h b/panda/src/pgraphnodes/lightLensNode.h index 4398f02bfd..f045c539b3 100644 --- a/panda/src/pgraphnodes/lightLensNode.h +++ b/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 ©); 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; diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index cf8f0783d4..16a7483478 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/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; From f3ba1d317c7b9e1b3d424a32f1ae86f2404713a7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 16:10:56 +0100 Subject: [PATCH 14/23] device: support for Steam Controller on Linux Adds lgrip and rgrip button handles, which are present on Steam Controller but also on the Oculus Touch --- panda/src/device/evdevInputDevice.cxx | 43 ++++++++++++++++++++++-- panda/src/device/evdevInputDevice.h | 4 ++- panda/src/device/linuxJoystickDevice.cxx | 25 +++++++++++--- panda/src/device/linuxJoystickDevice.h | 2 +- panda/src/putil/gamepadButton.cxx | 4 +++ panda/src/putil/gamepadButton.h | 2 ++ 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 610fb8722a..057d9b14ff 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -57,6 +57,9 @@ enum QuirkBits { // ABS_THROTTLE maps to rudder QB_rudder_from_throttle = 16, + + // Special handling for Steam Controller, which has many peculiarities. + QB_steam_controller = 32, }; static const struct DeviceMapping { @@ -71,6 +74,10 @@ 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 @@ -299,6 +306,13 @@ 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; + } + } + // Try to detect which type of device we have here if (_device_class == DeviceClass::unknown) { int device_scores[(size_t)DeviceClass::spatial_mouse] = {0}; @@ -378,7 +392,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()) { @@ -527,6 +541,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. @@ -740,7 +766,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[] = { @@ -897,7 +923,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(), @@ -991,6 +1021,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(); } diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 53c495ad08..994915e40b 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -64,7 +64,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() { diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 1871a1e948..c964ea8499 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/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)); diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index af0a74e3e6..61d05cd3e8 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/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; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 2924f0bf90..4e3c8be22c 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/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"); diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 774ee8ef2f..ff89c7e294 100644 --- a/panda/src/putil/gamepadButton.h +++ b/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(); From 6559932c7b7c6e02af4ae57e37c634c79d73f526 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 16:27:21 +0100 Subject: [PATCH 15/23] ShaderTerrainMesh: silently reload texture if it has no RAM image Fixes #492 --- panda/src/grutil/shaderTerrainMesh.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panda/src/grutil/shaderTerrainMesh.cxx b/panda/src/grutil/shaderTerrainMesh.cxx index a0f3c34690..e8078f7b68 100644 --- a/panda/src/grutil/shaderTerrainMesh.cxx +++ b/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); From c61b480a41ae5f83f06101aeedb10a41a2ee02bb Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 17:07:42 +0100 Subject: [PATCH 16/23] Revert "bam2egg: support exposed joints properly" This reverts commit dee4cd62017853f44bb54f6e6c764ba2b18cf90f. Fixes #237 --- panda/src/egg2pg/eggSaver.cxx | 49 ++++------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/panda/src/egg2pg/eggSaver.cxx b/panda/src/egg2pg/eggSaver.cxx index 9aba56cc01..451497381c 100644 --- a/panda/src/egg2pg/eggSaver.cxx +++ b/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); From 2bde2baed23f1c0d350b0b0232c0a796326d3d4e Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:23:16 +0100 Subject: [PATCH 17/23] device: a couple of fixes with Windows device input --- panda/src/device/winInputDeviceManager.cxx | 1 - panda/src/device/winRawInputDevice.cxx | 102 ++++++++++++--------- panda/src/device/winRawInputDevice.h | 1 + panda/src/device/xInputDevice.cxx | 12 +++ 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index b7fefc7c3e..765fb81816 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -277,7 +277,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); } diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index f49a7e0d7f..eeb98f112d 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -604,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); @@ -621,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]; + process_report((PCHAR)ptr, input->data.hid.dwSizeHid); + ptr += input->data.hid.dwSizeHid; + } +} - 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); - } +/** + * 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); } - 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 } + 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"; } } diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h index c5cba8c615..8c880604be 100644 --- a/panda/src/device/winRawInputDevice.h +++ b/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(); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 42f9c4fbef..4621d467b9 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/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 { From a02920bb42dbd3dc0c7e6e83bf0fd6c14baee50a Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:24:07 +0100 Subject: [PATCH 18/23] device: detect XInput devices on start-up This is needed to handle cases where XInput is being emulated, such as when running via Steam with the Steam Controller active. Steam's shim does not send out WM_INPUT_DEVICE_CHANGE events. --- panda/src/device/winInputDeviceManager.cxx | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index 765fb81816..033a174f6b 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/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); + } } /** From 4bf10925b399753b62f5ddd70c4278d579ffb944 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:31:11 +0100 Subject: [PATCH 19/23] samples: prevent duplicate connect assertion in device_tester.py --- samples/gamepad/device_tester.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/gamepad/device_tester.py b/samples/gamepad/device_tester.py index a3f70b96b4..ee2fad6c30 100644 --- a/samples/gamepad/device_tester.py +++ b/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) From ac8be1a2e684f76bfc3ebae93381c711ef80b749 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:32:04 +0100 Subject: [PATCH 20/23] py_panda: fix int-to-enum conversion in Python 2 Fixes #498 --- dtool/src/interrogatedb/py_panda.cxx | 58 ++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 7c2bef1ce7..6ebfff5b4b 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/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) { From dccf8074fde6bbfd394590b47499f2ade9be8af8 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 21:49:38 +0100 Subject: [PATCH 21/23] android: fix compile errors in androiddisplay module --- .../androiddisplay/androidGraphicsWindow.cxx | 32 +++++++++---------- .../androiddisplay/androidGraphicsWindow.h | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/panda/src/androiddisplay/androidGraphicsWindow.cxx b/panda/src/androiddisplay/androidGraphicsWindow.cxx index 431f9419b7..cc04022e81 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.cxx +++ b/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; } diff --git a/panda/src/androiddisplay/androidGraphicsWindow.h b/panda/src/androiddisplay/androidGraphicsWindow.h index 0842b18f2b..f8b75421f6 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.h +++ b/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() { From c365beaf1134623db30b2e435d2fb4e67ed6d8a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 22:07:46 +0100 Subject: [PATCH 22/23] device: hide Steam Controller on Linux while Steam is running When Steam runs, it mutes all input from the Steam Controller and instead registers a virtual gamepad that obeys the Big Picture controller remapping. We want to make sure to pick that one, since the "real" Steam Controller doesn't report any events while the virtual device is active. So we carefully consider the "real" Steam Controller "inactive" if the presence of the virtual device is detected, until the virtual device is disconnected (ie. when Steam is closed) or if it turns out that it is generating events after all, in which case it gets automatically reactivated. This is a bit of a hack, maybe there is a cleaner way of doing this. --- panda/src/device/evdevInputDevice.cxx | 38 +++++++++- panda/src/device/evdevInputDevice.h | 7 ++ panda/src/device/linuxInputDeviceManager.cxx | 75 ++++++++++++++++++++ panda/src/device/linuxInputDeviceManager.h | 3 + 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 057d9b14ff..b137888d6d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -59,6 +59,8 @@ enum QuirkBits { 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, }; @@ -119,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); @@ -217,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; @@ -228,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); @@ -310,8 +333,19 @@ init_device() { 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) { diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 994915e40b..ffc3503ff0 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/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; @@ -77,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; diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 45a83bfdb3..8eed3e1cc3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/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 diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h index c5ad6a013f..36fce94842 100644 --- a/panda/src/device/linuxInputDeviceManager.h +++ b/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: From 6e951773a6456c4af0f1614ba580170945a21a7c Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 2 Jan 2019 01:35:18 -0700 Subject: [PATCH 23/23] pipeline: (Light)ReMutexDirect should include thread.h Thread methods are used in the respective .I files, so an incomplete forward declaration won't suffice here. --- panda/src/pipeline/lightReMutexDirect.h | 3 +-- panda/src/pipeline/reMutexDirect.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/panda/src/pipeline/lightReMutexDirect.h b/panda/src/pipeline/lightReMutexDirect.h index d1bc022740..384330587e 100644 --- a/panda/src/pipeline/lightReMutexDirect.h +++ b/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 diff --git a/panda/src/pipeline/reMutexDirect.h b/panda/src/pipeline/reMutexDirect.h index faf46d0fb0..9a82b728ba 100644 --- a/panda/src/pipeline/reMutexDirect.h +++ b/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