diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index 4d6dec5b81..974c611bcc 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -7,7 +7,7 @@ from direct.distributed.DoCollectionManager import DoCollectionManager from direct.showbase import GarbageReport from .PyDatagramIterator import PyDatagramIterator -import types +import inspect import gc __all__ = ["ConnectionRepository", "GCTrigger"] @@ -327,13 +327,13 @@ class ConnectionRepository( if classDef is None: self.notify.debug("No class definition for %s." % (className)) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.warning("Module %s does not define class %s." % (className, className)) continue classDef = getattr(classDef, className) - if type(classDef) != types.ClassType and type(classDef) != types.TypeType: + if inspect.isclass(classDef): self.notify.error("Symbol %s is not a class name." % (className)) else: dclass.setClassDef(classDef) @@ -388,7 +388,7 @@ class ConnectionRepository( if classDef is None: self.notify.error("No class definition for %s." % className) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.error("Module %s does not define class %s." % (className, className)) classDef = getattr(classDef, className) diff --git a/direct/src/distributed/PyDatagramIterator.py b/direct/src/distributed/PyDatagramIterator.py index e97600f6b4..60267a1ab9 100755 --- a/direct/src/distributed/PyDatagramIterator.py +++ b/direct/src/distributed/PyDatagramIterator.py @@ -75,7 +75,7 @@ class PyDatagramIterator(DatagramIterator): b = self.getUint8() retVal.append((a, b)) else: - raise Exception("Error: No such type as: " + str(subAtomicType)) + raise Exception("Error: No such type as: " + str(subatomicType)) else: # See if it is in the handy dict getFunc = self.FuncDict.get(subatomicType) @@ -121,8 +121,4 @@ class PyDatagramIterator(DatagramIterator): else: raise Exception("Error: No such type as: " + str(subatomicType)) - - return retVal - - diff --git a/direct/src/distributed/ServerRepository.py b/direct/src/distributed/ServerRepository.py index 4031998b50..7783e2860b 100644 --- a/direct/src/distributed/ServerRepository.py +++ b/direct/src/distributed/ServerRepository.py @@ -7,6 +7,8 @@ from direct.task import Task from direct.directnotify import DirectNotifyGlobal from direct.distributed.PyDatagram import PyDatagram +import inspect + class ServerRepository: @@ -273,12 +275,12 @@ class ServerRepository: if classDef == None: self.notify.debug("No class definition for %s." % (className)) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.error("Module %s does not define class %s." % (className, className)) classDef = getattr(classDef, className) - if type(classDef) != types.ClassType and type(classDef) != types.TypeType: + if inspect.isclass(classDef): self.notify.error("Symbol %s is not a class name." % (className)) else: dclass.setClassDef(classDef) diff --git a/direct/src/interval/MetaInterval.py b/direct/src/interval/MetaInterval.py index fd379280a7..259d7f66a3 100644 --- a/direct/src/interval/MetaInterval.py +++ b/direct/src/interval/MetaInterval.py @@ -342,7 +342,6 @@ class MetaInterval(CMetaInterval): # with all of their associated Python callbacks: def setManager(self, manager): - rogerroger self.__manager = manager CMetaInterval.setManager(self, manager) diff --git a/direct/src/showbase/ObjectPool.py b/direct/src/showbase/ObjectPool.py index e5970803ab..e82e6cdaae 100755 --- a/direct/src/showbase/ObjectPool.py +++ b/direct/src/showbase/ObjectPool.py @@ -110,15 +110,6 @@ class ObjectPool: print('TYPE: %s, %s objects' % (repr(typ), len(self._type2objs[typ]))) print(getNumberedTypedSortedString(self._type2objs[typ])) - def containerLenStr(self): - s = 'Object Pool: Container Lengths' - s += '\n==============================' - lengths = list(self._len2obj.keys()) - lengths.sort() - lengths.reverse() - for count in counts: - pass - def printReferrers(self, numEach=3): """referrers of the first few of each type of object""" counts = list(set(self._count2types.keys())) diff --git a/direct/src/showbase/TkGlobal.py b/direct/src/showbase/TkGlobal.py index bfdeb9b48f..673d337e3c 100644 --- a/direct/src/showbase/TkGlobal.py +++ b/direct/src/showbase/TkGlobal.py @@ -12,5 +12,28 @@ else: if '_Pmw' in sys.modules: sys.modules['_Pmw'].__name__ = '_Pmw' +# Hack to workaround broken Pmw.NoteBook in Python 3 +def bordercolors(root, colorName): + lightRGB = [] + darkRGB = [] + for value in Pmw.Color.name2rgb(root, colorName, 1): + value40pc = (14 * value) // 10 + if value40pc > int(Pmw.Color._MAX_RGB): + value40pc = int(Pmw.Color._MAX_RGB) + valueHalfWhite = (int(Pmw.Color._MAX_RGB) + value) // 2; + lightRGB.append(max(value40pc, valueHalfWhite)) + + darkValue = (60 * value) // 100 + darkRGB.append(darkValue) + + return ( + '#%04x%04x%04x' % (lightRGB[0], lightRGB[1], lightRGB[2]), + '#%04x%04x%04x' % (darkRGB[0], darkRGB[1], darkRGB[2]) + ) + +Pmw.Color.bordercolors = bordercolors +del bordercolors + + def spawnTkLoop(): base.spawnTkLoop() diff --git a/direct/src/stdpy/threading.py b/direct/src/stdpy/threading.py index b4cb6d9228..466a198a3a 100644 --- a/direct/src/stdpy/threading.py +++ b/direct/src/stdpy/threading.py @@ -302,7 +302,7 @@ class BoundedSemaphore(Semaphore): Semaphore.__init__(value) def release(self): - if self.getCount() > value: + if self.getCount() > self.__max: raise ValueError Semaphore.release(self) diff --git a/dtool/src/dtoolbase/dtoolsymbols.h b/dtool/src/dtoolbase/dtoolsymbols.h index 971a13ce32..892e1a9fff 100644 --- a/dtool/src/dtoolbase/dtoolsymbols.h +++ b/dtool/src/dtoolbase/dtoolsymbols.h @@ -67,8 +67,6 @@ can define all of these stupid symbols to the empty string. */ -#define EXPCL_EMPTY - #ifdef BUILDING_DTOOL #define EXPCL_DTOOL EXPORT_CLASS #define EXPTP_DTOOL EXPORT_TEMPL diff --git a/dtool/src/dtoolbase/typeHandle.I b/dtool/src/dtoolbase/typeHandle.I index e002fbb1d7..3e9e53a9ae 100644 --- a/dtool/src/dtoolbase/typeHandle.I +++ b/dtool/src/dtoolbase/typeHandle.I @@ -194,9 +194,9 @@ output(ostream &out) const { /** * Returns a special zero-valued TypeHandle that is used to indicate no type. */ -INLINE TypeHandle TypeHandle:: +CONSTEXPR TypeHandle TypeHandle:: none() { - return _none; + return TypeHandle(0); } /** @@ -213,9 +213,15 @@ operator bool () const { * * See TypeRegistry::find_type_by_id(). */ -INLINE TypeHandle TypeHandle:: +CONSTEXPR TypeHandle TypeHandle:: from_index(int index) { - TypeHandle handle; - handle._index = index; - return handle; + return TypeHandle(index); +} + +/** + * Private constructor for initializing a TypeHandle from an index, used by + * none() and by from_index(). + */ +CONSTEXPR TypeHandle:: +TypeHandle(int index) : _index(index) { } diff --git a/dtool/src/dtoolbase/typeHandle.h b/dtool/src/dtoolbase/typeHandle.h index f770b1810b..58e69646f4 100644 --- a/dtool/src/dtoolbase/typeHandle.h +++ b/dtool/src/dtoolbase/typeHandle.h @@ -80,6 +80,8 @@ class TypedObject; */ class EXPCL_DTOOL TypeHandle FINAL { PUBLISHED: + TypeHandle() NOEXCEPT DEFAULT_CTOR; + enum MemoryClass { MC_singleton, MC_array, @@ -127,7 +129,7 @@ PUBLISHED: INLINE int get_index() const; INLINE void output(ostream &out) const; - INLINE static TypeHandle none(); + CONSTEXPR static TypeHandle none(); INLINE operator bool () const; MAKE_PROPERTY(index, get_index); @@ -140,12 +142,15 @@ public: void *reallocate_array(void *ptr, size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT); void deallocate_array(void *ptr); - INLINE static TypeHandle from_index(int index); + CONSTEXPR static TypeHandle from_index(int index); private: - int _index; + CONSTEXPR TypeHandle(int index); + + // Only kept temporarily for ABI compatibility. static TypeHandle _none; + int _index; friend class TypeRegistry; }; diff --git a/dtool/src/newheader/newheader.cxx b/dtool/src/newheader/newheader.cxx deleted file mode 100644 index 2e97dbb031..0000000000 --- a/dtool/src/newheader/newheader.cxx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * PANDA 3D SOFTWARE - * Copyright (c) Carnegie Mellon University. All rights reserved. - * - * All use of this software is subject to the terms of the revised BSD - * license. You should have received a copy of this license along - * with this source code in a file named "LICENSE." - * - * @file newheader.cxx - * @author drose - * @date 2004-07-05 - */ - -#include "dtoolbase.h" - -#include -#include -#include - -const char *cxx_style = -"// Filename: %s\n" -"// Created by: %s (%s)\n" -"//\n" -"////////////////////////////////////////////////////////////////////\n" -"//\n" -"// PANDA 3D SOFTWARE\n" -"// Copyright (c) Carnegie Mellon University. All rights reserved.\n" -"//\n" -"// All use of this software is subject to the terms of the revised BSD\n" -"// license. You should have received a copy of this license along\n" -"// with this source code in a file named \"LICENSE.\"\n" -"//\n" -"////////////////////////////////////////////////////////////////////\n" -"\n"; - -const char *c_style = -"/* Filename: %s\n" -" * Created by: %s (%s)\n" -" *\n" -" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" -" *\n" -" * PANDA 3D SOFTWARE\n" -" * Copyright (c) Carnegie Mellon University. All rights reserved.\n" -" *\n" -" * All use of this software is subject to the terms of the revised BSD\n" -" * license. You should have received a copy of this license along\n" -" * with this source code in a file named \"LICENSE.\"\n" -" *\n" -" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */\n" -"\n"; - -struct FileDef { - const char *extension; - const char *header; -}; - -FileDef file_def[] = { - { "h", cxx_style }, - { "cxx", cxx_style }, - { "I", cxx_style }, - { "T", cxx_style }, - { "c", c_style }, - { NULL, NULL }, -}; - -void -generate_header(const char *header, const string &filename) { - const char *username = getenv("USER"); - if (username == NULL) { - username = ""; - } - - static const size_t max_date_buffer = 128; - char date_buffer[max_date_buffer]; - time_t now = time(NULL); - strftime(date_buffer, max_date_buffer, "%d%b%y", localtime(&now)); - - printf(header, filename.c_str(), username, date_buffer); -} - -int -main(int argc, char *argv[]) { - if (argc < 2) { - cerr << "Must specify the filename to generate a header for.\n"; - exit(1); - } - - string filename = argv[1]; - size_t dot = filename.rfind('.'); - if (dot == string::npos) { - // No extension, no header. - return 0; - } - - string extension = filename.substr(dot + 1); - - size_t i = 0; - while (file_def[i].extension != NULL) { - if (extension == file_def[i].extension) { - generate_header(file_def[i].header, filename); - return 0; - } - i++; - } - - // No matching extension, no problem. - return 0; -} diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 9a9d1451c0..e5e3c289d3 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -5179,7 +5179,7 @@ if (PkgSkip("SPEEDTREE")==0): # DIRECTORY: panda/src/testbed/ # -if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0 and GetTarget() != 'android'): +if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0): OPTS=['DIR:panda/src/testbed'] TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx') TargetAdd('pview.exe', input='pview_pview.obj') @@ -5198,6 +5198,7 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('org/panda3d/android/NativeIStream.class', opts=OPTS, input='NativeIStream.java') TargetAdd('org/panda3d/android/NativeOStream.class', opts=OPTS, input='NativeOStream.java') TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java') + TargetAdd('org/panda3d/android/PythonActivity.class', opts=OPTS, input='PythonActivity.java') TargetAdd('p3android_composite1.obj', opts=OPTS, input='p3android_composite1.cxx') TargetAdd('libp3android.dll', input='p3android_composite1.obj') @@ -5208,10 +5209,10 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('android_main.obj', opts=OPTS, input='android_main.cxx') if (not RTDIST and PkgSkip("PVIEW")==0): - TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx') + TargetAdd('libpview_pview.obj', opts=OPTS, input='pview.cxx') TargetAdd('libpview.dll', input='android_native_app_glue.obj') TargetAdd('libpview.dll', input='android_main.obj') - TargetAdd('libpview.dll', input='pview_pview.obj') + TargetAdd('libpview.dll', input='libpview_pview.obj') TargetAdd('libpview.dll', input='libp3framework.dll') if not PkgSkip("EGG"): TargetAdd('libpview.dll', input='libpandaegg.dll') @@ -5219,6 +5220,17 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('libpview.dll', input=COMMON_PANDA_LIBS) TargetAdd('libpview.dll', opts=['MODULE', 'ANDROID']) + if (not RTDIST and PkgSkip("PYTHON")==0): + OPTS += ['PYTHON'] + TargetAdd('ppython_ppython.obj', opts=OPTS, input='python_main.cxx') + TargetAdd('libppython.dll', input='android_native_app_glue.obj') + TargetAdd('libppython.dll', input='android_main.obj') + TargetAdd('libppython.dll', input='ppython_ppython.obj') + TargetAdd('libppython.dll', input='libp3framework.dll') + TargetAdd('libppython.dll', input='libp3android.dll') + TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS) + TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID', 'PYTHON']) + # # DIRECTORY: panda/src/androiddisplay/ # @@ -7632,7 +7644,7 @@ def MakeInstallerAndroid(): continue if '.so.' in line: dep = line.rpartition('.so.')[0] + '.so' - oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target)) + oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) else: dep = line @@ -7643,6 +7655,7 @@ def MakeInstallerAndroid(): copy_library(os.path.realpath(fulldep), dep) break + # Now copy every lib in the lib dir, and its dependencies. for base in os.listdir(source_dir): if not base.startswith('lib'): continue @@ -7654,6 +7667,59 @@ def MakeInstallerAndroid(): continue copy_library(source, base) + # Same for Python extension modules. However, Android is strict about + # library naming, so we have a special naming scheme for these, in + # conjunction with a custom import hook to find these modules. + if not PkgSkip("PYTHON"): + suffix = GetExtensionSuffix() + source_dir = os.path.join(GetOutputDir(), "panda3d") + for base in os.listdir(source_dir): + if not base.endswith(suffix): + continue + modname = base[:-len(suffix)] + source = os.path.join(source_dir, base) + copy_library(source, "libpy.panda3d.{}.so".format(modname)) + + # Same for standard Python modules. + import _ctypes + source_dir = os.path.dirname(_ctypes.__file__) + for base in os.listdir(source_dir): + if not base.endswith('.so'): + continue + modname = base.partition('.')[0] + source = os.path.join(source_dir, base) + copy_library(source, "libpy.{}.so".format(modname)) + + def copy_python_tree(source_root, target_root): + for source_dir, dirs, files in os.walk(source_root): + if 'site-packages' in dirs: + dirs.remove('site-packages') + + if not any(base.endswith('.py') for base in files): + continue + + target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root)) + target_dir = os.path.normpath(target_dir) + os.makedirs(target_dir, 0o755) + + for base in files: + if base.endswith('.py'): + target = os.path.join(target_dir, base) + shutil.copy(os.path.join(source_dir, base), target) + + # Copy the Python standard library to the .apk as well. + from distutils.sysconfig import get_python_lib + stdlib_source = get_python_lib(False, True) + stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info)) + copy_python_tree(stdlib_source, stdlib_target) + + # But also copy over our custom site.py. + shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py")) + + # And now make a site-packages directory containing our direct/panda3d/pandac modules. + for tree in "panda3d", "direct", "pandac": + copy_python_tree(os.path.join(GetOutputDir(), tree), os.path.join(stdlib_target, "site-packages", tree)) + # Copy the models and config files to the virtual assets filesystem. oscmd("mkdir apkroot/assets") oscmd("cp -R %s apkroot/assets/models" % (os.path.join(GetOutputDir(), "models"))) @@ -7672,7 +7738,11 @@ def MakeInstallerAndroid(): oscmd(aapt_cmd) # And add all the libraries to it. - oscmd("cd apkroot && aapt add ../%s classes.dex lib/%s/lib*.so" % (apk_unaligned, SDK["ANDROID_ABI"])) + oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned)) + for path, dirs, files in os.walk('apkroot/lib'): + if files: + rel = os.path.relpath(path, 'apkroot') + oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel)) # Now align the .apk, which is necessary for Android to load it. oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned)) diff --git a/panda/src/android/PandaActivity.java b/panda/src/android/PandaActivity.java index a4413a2816..36a8c97521 100644 --- a/panda/src/android/PandaActivity.java +++ b/panda/src/android/PandaActivity.java @@ -87,9 +87,9 @@ public class PandaActivity extends NativeActivity { return path; } - public String getIntentOutputPath() { + public String getIntentOutputUri() { Intent intent = getIntent(); - return intent.getStringExtra("org.panda3d.OUTPUT_PATH"); + return intent.getStringExtra("org.panda3d.OUTPUT_URI"); } public String getCacheDirString() { diff --git a/panda/src/android/PythonActivity.java b/panda/src/android/PythonActivity.java new file mode 100644 index 0000000000..0d282d84a5 --- /dev/null +++ b/panda/src/android/PythonActivity.java @@ -0,0 +1,23 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file PythonActivity.java + * @author rdb + * @date 2018-02-04 + */ + +package org.panda3d.android; + +import org.panda3d.android.PandaActivity; + +/** + * This is only declared as a separate class from PandaActivity so that we + * can have two separate activity definitions in ApplicationManifest.xml. + */ +public class PythonActivity extends PandaActivity { +} diff --git a/panda/src/android/android_main.cxx b/panda/src/android/android_main.cxx index 7d4e75e4b2..0565ef5835 100644 --- a/panda/src/android/android_main.cxx +++ b/panda/src/android/android_main.cxx @@ -17,11 +17,14 @@ #include "virtualFileSystem.h" #include "filename.h" #include "thread.h" +#include "urlSpec.h" #include "config_display.h" // #define OPENGLES_1 #include "config_androiddisplay.h" #include +#include +#include // struct android_app* panda_android_app = NULL; @@ -67,6 +70,55 @@ void android_main(struct android_app* app) { android_cat.info() << "New native activity started on " << *current_thread << "\n"; + // Were we given an optional location to write the stdout/stderr streams? + methodID = env->GetMethodID(activity_class, "getIntentOutputUri", "()Ljava/lang/String;"); + jstring joutput_uri = (jstring) env->CallObjectMethod(activity->clazz, methodID); + if (joutput_uri != nullptr) { + const char *output_uri = env->GetStringUTFChars(joutput_uri, nullptr); + + if (output_uri != nullptr && output_uri[0] != 0) { + URLSpec spec(output_uri); + + if (spec.get_scheme() == "file") { + string path = spec.get_path(); + int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY); + if (fd != -1) { + android_cat.info() + << "Writing standard output to file " << path << "\n"; + + dup2(fd, 1); + dup2(fd, 2); + } else { + android_cat.error() + << "Failed to open output path " << path << "\n"; + } + } else if (spec.get_scheme() == "tcp") { + string host = spec.get_server(); + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in serv_addr = {0}; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(spec.get_port()); + serv_addr.sin_addr.s_addr = inet_addr(host.c_str()); + if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + android_cat.info() + << "Writing standard output to socket " + << spec.get_server_and_port() << "\n"; + dup2(fd, 1); + dup2(fd, 2); + } else { + android_cat.error() + << "Failed to open output socket " + << spec.get_server_and_port() << "\n"; + } + close(fd); + } else { + android_cat.error() + << "Unsupported scheme in output URI: " << output_uri << "\n"; + } + env->ReleaseStringUTFChars(joutput_uri, output_uri); + } + } + // Fetch the data directory. jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); @@ -186,28 +238,6 @@ void android_main(struct android_app* app) { } } - // Were we given an optional location to write the stdout/stderr streams? - methodID = env->GetMethodID(activity_class, "getIntentOutputPath", "()Ljava/lang/String;"); - jstring joutput_path = (jstring) env->CallObjectMethod(activity->clazz, methodID); - if (joutput_path != nullptr) { - const char *output_path = env->GetStringUTFChars(joutput_path, nullptr); - - if (output_path != nullptr && output_path[0] != 0) { - int fd = open(output_path, O_CREAT | O_TRUNC | O_WRONLY); - if (fd != -1) { - android_cat.info() - << "Writing standard output to file " << output_path << "\n"; - - dup2(fd, 1); - dup2(fd, 2); - } else { - android_cat.error() - << "Failed to open output path " << output_path << "\n"; - } - env->ReleaseStringUTFChars(joutput_path, output_path); - } - } - // Create bogus argc and argv for calling the main function. const char *argv[] = {"pview", nullptr, nullptr}; int argc = 1; @@ -266,6 +296,9 @@ void android_main(struct android_app* app) { env->ReleaseStringUTFChars(filename, filename_str); } + close(1); + close(2); + // Detach the thread before exiting. activity->vm->DetachCurrentThread(); } diff --git a/panda/src/android/pview_manifest.xml b/panda/src/android/pview_manifest.xml index 30125c673f..b462e4018a 100644 --- a/panda/src/android/pview_manifest.xml +++ b/panda/src/android/pview_manifest.xml @@ -7,6 +7,7 @@ + @@ -44,13 +45,38 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panda/src/android/python_main.cxx b/panda/src/android/python_main.cxx new file mode 100644 index 0000000000..c1fc0a39fe --- /dev/null +++ b/panda/src/android/python_main.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file python_main.cxx + * @author rdb + * @date 2018-02-12 + */ + +#include "dtoolbase.h" +#include "config_android.h" +#include "executionEnvironment.h" + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#include +#if PY_MAJOR_VERSION >= 3 +#include +#endif + +#include + +/** + * The main entry point for the Python activity. Called by android_main. + */ +int main(int argc, char *argv[]) { + if (argc <= 1) { + return 1; + } + + // Help out Python by telling it which encoding to use + Py_FileSystemDefaultEncoding = "utf-8"; + + Py_SetProgramName(Py_DecodeLocale("ppython", nullptr)); + + // Set PYTHONHOME to the location of the .apk file. + string apk_path = ExecutionEnvironment::get_binary_name(); + Py_SetPythonHome(Py_DecodeLocale(apk_path.c_str(), nullptr)); + + // We need to make zlib available to zipimport, but I don't know how + // we could inject our import hook before Py_Initialize, so instead + // load it as though it were a built-in module. + void *zlib = dlopen("libpy.zlib.so", RTLD_NOW); + if (zlib != nullptr) { + void *init = dlsym(zlib, "PyInit_zlib"); + if (init != nullptr) { + PyImport_AppendInittab("zlib", (PyObject *(*)())init); + } + } + + Py_Initialize(); + + // This is used by the import hook to locate the module libraries. + Filename dtool_name = ExecutionEnvironment::get_dtool_name(); + string native_dir = dtool_name.get_dirname(); + PyObject *py_native_dir = PyUnicode_FromStringAndSize(native_dir.c_str(), native_dir.size()); + PySys_SetObject("_native_library_dir", py_native_dir); + Py_DECREF(py_native_dir); + + int sts = 1; + FILE *fp = fopen(argv[1], "r"); + if (fp != nullptr) { + int res = PyRun_AnyFile(fp, argv[1]); + if (res > 0) { + sts = 0; + } else { + android_cat.error() << "Error running " << argv[1] << "\n"; + PyErr_Print(); + } + } else { + android_cat.error() << "Unable to open " << argv[1] << "\n"; + } + + Py_Finalize(); + return sts; +} diff --git a/panda/src/android/run_pview.sh b/panda/src/android/run_pview.sh new file mode 100755 index 0000000000..6f3b6f1476 --- /dev/null +++ b/panda/src/android/run_pview.sh @@ -0,0 +1,14 @@ +# This script can be used for launching the Panda viewer from the Android +# terminal environment, for example from within termux. It uses a socket +# to pipe the command-line output back to the terminal. + +port=12345 + +if [[ $# -eq 0 ]] ; then + echo "Pass full path of model" + exit 1 +fi + +am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PandaActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1) + +nc -l -p $port diff --git a/panda/src/android/run_python.sh b/panda/src/android/run_python.sh new file mode 100755 index 0000000000..9a8adc710b --- /dev/null +++ b/panda/src/android/run_python.sh @@ -0,0 +1,14 @@ +# This script can be used for launching a Python script from the Android +# terminal environment, for example from within termux. It uses a socket +# to pipe the command-line output back to the terminal. + +port=12345 + +if [[ $# -eq 0 ]] ; then + echo "Pass full path of script" + exit 1 +fi + +am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PythonActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1) + +nc -l -p $port diff --git a/panda/src/android/site.py b/panda/src/android/site.py new file mode 100644 index 0000000000..fd3909ede8 --- /dev/null +++ b/panda/src/android/site.py @@ -0,0 +1,34 @@ +import sys +import os + +from importlib.abc import Loader, MetaPathFinder +from importlib.machinery import ModuleSpec + +if sys.version_info >= (3, 5): + from importlib import _bootstrap_external +else: + from importlib import _bootstrap as _bootstrap_external + +sys.platform = "android" + +class AndroidExtensionFinder(MetaPathFinder): + @classmethod + def find_spec(cls, fullname, path=None, target=None): + soname = 'libpy.' + fullname + '.so' + path = os.path.join(sys._native_library_dir, soname) + + if os.path.exists(path): + loader = _bootstrap_external.ExtensionFileLoader(fullname, path) + return ModuleSpec(fullname, loader, origin=path) + + +def main(): + """Adds the site-packages directory to the sys.path. + Also, registers the import hook for extension modules.""" + + sys.path.append('{0}/lib/python{1}.{2}/site-packages'.format(sys.prefix, *sys.version_info)) + sys.meta_path.append(AndroidExtensionFinder) + + +if not sys.flags.no_site: + main() diff --git a/panda/src/androiddisplay/androidGraphicsWindow.cxx b/panda/src/androiddisplay/androidGraphicsWindow.cxx index 31cd4e23d6..1a91f21186 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.cxx +++ b/panda/src/androiddisplay/androidGraphicsWindow.cxx @@ -309,8 +309,6 @@ open_window() { _fb_properties = androidgsg->get_fb_properties(); - androiddisplay_cat.error() << "open_window done\n"; - return true; } @@ -366,7 +364,6 @@ create_surface() { // Create a context. if (androidgsg->_context == EGL_NO_CONTEXT) { - androiddisplay_cat.error() << "creating context\n"; if (!androidgsg->create_context()) { return false; } diff --git a/panda/src/audiotraits/config_openalAudio.cxx b/panda/src/audiotraits/config_openalAudio.cxx index 4f9019c013..a3340477c2 100644 --- a/panda/src/audiotraits/config_openalAudio.cxx +++ b/panda/src/audiotraits/config_openalAudio.cxx @@ -30,6 +30,23 @@ ConfigVariableString openal_device PRC_DESC("Specify the OpenAL device string for audio playback (no quotes). If this " "is not specified, the OpenAL default device is used.")); +ConfigVariableInt openal_buffer_delete_retries +("openal-buffer-delete-retries", 5, + PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " + "sound plugin will wait a moment and retry deletion, with an " + "exponentially-increasing delay for each try. This number " + "specifies how many repeat tries (not counting the initial try) " + "should be made before giving up and raising an error.")); + +ConfigVariableDouble openal_buffer_delete_delay +("openal-buffer-delete-delay", 0.001, + PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " + "sound plugin will wait a moment and retry deletion, with an " + "exponentially-increasing delay for each try. This number " + "specifies how long, in seconds, the OpenAL plugin will wait after " + "its first failed try. The second try will be double this " + "delay, the third quadruple, and so on.")); + /** * Initializes the library. This must be called at least once before any of diff --git a/panda/src/audiotraits/config_openalAudio.h b/panda/src/audiotraits/config_openalAudio.h index bcd8481acf..6817362e31 100644 --- a/panda/src/audiotraits/config_openalAudio.h +++ b/panda/src/audiotraits/config_openalAudio.h @@ -26,5 +26,7 @@ extern "C" EXPCL_OPENAL_AUDIO void init_libOpenALAudio(); extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio(); extern ConfigVariableString openal_device; +extern ConfigVariableInt openal_buffer_delete_retries; +extern ConfigVariableDouble openal_buffer_delete_delay; #endif // CONFIG_OPENALAUDIO_H diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index cbdaadbfa1..2ba02c2fa7 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -524,7 +524,7 @@ get_sound(const string &file_name, bool positional, int mode) { void OpenALAudioManager:: uncache_sound(const string& file_name) { ReMutexHolder holder(_lock); - assert(is_valid()); + nassertv(is_valid()); Filename path = file_name; VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); @@ -910,7 +910,7 @@ reduce_sounds_playing_to(unsigned int count) { int limit = _sounds_playing.size() - count; while (limit-- > 0) { SoundsPlaying::iterator sound = _sounds_playing.begin(); - assert(sound != _sounds_playing.end()); + nassertv(sound != _sounds_playing.end()); // When the user stops a sound, there is still a PT in the user's hand. // When we stop a sound here, however, this can remove the last PT. This // can cause an ugly recursion where stop calls the destructor, and the @@ -1052,7 +1052,7 @@ OpenALAudioManager::SoundData:: if (_sample != 0) { if (_manager->_is_valid) { _manager->make_current(); - alDeleteBuffers(1,&_sample); + _manager->delete_buffer(_sample); } _sample = 0; } @@ -1111,8 +1111,8 @@ discard_excess_cache(int sample_limit) { while (((int)_expiring_samples.size()) > sample_limit) { SoundData *sd = (SoundData*)(_expiring_samples.front()); - assert(sd->_client_count == 0); - assert(sd->_expire == _expiring_samples.begin()); + nassertv(sd->_client_count == 0); + nassertv(sd->_expire == _expiring_samples.begin()); _expiring_samples.pop_front(); _sample_cache.erase(_sample_cache.find(sd->_movie->get_filename())); audio_debug("Expiring: " << sd->_movie->get_filename().get_basename()); @@ -1121,10 +1121,49 @@ discard_excess_cache(int sample_limit) { while (((int)_expiring_streams.size()) > stream_limit) { SoundData *sd = (SoundData*)(_expiring_streams.front()); - assert(sd->_client_count == 0); - assert(sd->_expire == _expiring_streams.begin()); + nassertv(sd->_client_count == 0); + nassertv(sd->_expire == _expiring_streams.begin()); _expiring_streams.pop_front(); audio_debug("Expiring: " << sd->_movie->get_filename().get_basename()); delete sd; } } + +/** + * Deletes an OpenAL buffer. This is a special function because some + * implementations of OpenAL (e.g. Apple's) don't unlock the buffers + * immediately, due to needing to coordinate with another thread. If this is + * the case, the alDeleteBuffers call will error back with AL_INVALID_OPERATION + * as if trying to delete an actively-used buffer, which will tell us to wait a + * bit and try again. + */ +void OpenALAudioManager:: +delete_buffer(ALuint buffer) { + ReMutexHolder holder(_lock); + int tries = 0; + ALuint error; + + // Keep trying until we succeed (or give up). + while (true) { + alDeleteBuffers(1, &buffer); + error = alGetError(); + + if (error == AL_NO_ERROR) { + // Success! This will happen right away 99% of the time. + return; + } else if (error != AL_INVALID_OPERATION) { + // We weren't expecting that. This should be reported. + break; + } else if (tries >= openal_buffer_delete_retries.get_value()) { + // We ran out of retries. Give up. + break; + } else { + // Make another try after (delay * 2^n) seconds. + Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << tries)); + tries++; + } + } + + // If we got here, one of the breaks above happened, indicating an error. + audio_error("failed to delete a buffer: " << alGetString(error) ); +} diff --git a/panda/src/audiotraits/openalAudioManager.h b/panda/src/audiotraits/openalAudioManager.h index 5917900869..1e16e4000b 100644 --- a/panda/src/audiotraits/openalAudioManager.h +++ b/panda/src/audiotraits/openalAudioManager.h @@ -129,6 +129,8 @@ private: void decrement_client_count(SoundData *sd); void discard_excess_cache(int limit); + void delete_buffer(ALuint buffer); + void starting_sound(OpenALAudioSound* audio); void stopping_sound(OpenALAudioSound* audio); diff --git a/panda/src/audiotraits/openalAudioSound.I b/panda/src/audiotraits/openalAudioSound.I index 1b7e56a82c..9a090adc3d 100644 --- a/panda/src/audiotraits/openalAudioSound.I +++ b/panda/src/audiotraits/openalAudioSound.I @@ -40,7 +40,7 @@ get_calibrated_clock(double rtc) const { * * Returns true on success, false on failure. */ -bool OpenALAudioSound:: +INLINE bool OpenALAudioSound:: require_sound_data() { if (_sd==0) { _sd = _manager->get_sound_data(_movie, _desired_mode); @@ -55,11 +55,48 @@ require_sound_data() { /** * Checks if the sound data record is present and releasable, and if so, * releases it. + * + * The sound data is "releasable" if it's from an ordinary, local file. Remote + * streams cannot necessarily be reopened if lost, so we'll hold onto them if + * so. The `force` argument overrides this, indicating we don't intend to + * reacquire the sound data. */ -void OpenALAudioSound:: -release_sound_data() { - if ((_sd!=0) && (!_movie->get_filename().empty())) { +INLINE void OpenALAudioSound:: +release_sound_data(bool force) { + if (!has_sound_data()) return; + + if (force || !_movie->get_filename().empty()) { _manager->decrement_client_count(_sd); _sd = 0; } } + +/** + * Checks if the sound has NOT been cleaned up yet. + */ +INLINE bool OpenALAudioSound:: +is_valid() const { + return _manager != NULL; +} + +/** + * Checks if the sound is playing. This is per the OpenALAudioManager's + * definition of "playing" -- as in, "will be called upon every update" + * + * This is mainly intended for use in asserts. + */ +INLINE bool OpenALAudioSound:: +is_playing() const { + // Manager only gives us a _source if we need it (to talk to OpenAL), so: + return _source != 0; +} + +/** + * Checks if the sound has its SoundData structure open at the moment. + * + * This is mainly intended for use in asserts. + */ +INLINE bool OpenALAudioSound:: +has_sound_data() const { + return _sd != NULL; +} diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index d3c5ff7291..faa9df5717 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -80,7 +80,7 @@ OpenALAudioSound(OpenALAudioManager* manager, audio_warning("stereo sound " << movie->get_filename() << " will not be spatialized"); } } - release_sound_data(); + release_sound_data(false); } @@ -99,15 +99,14 @@ OpenALAudioSound:: void OpenALAudioSound:: cleanup() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) { + if (!is_valid()) { return; } - if (_source) { + if (is_playing()) { stop(); } - if (_sd) { - _manager->decrement_client_count(_sd); - _sd = 0; + if (has_sound_data()) { + release_sound_data(true); } _manager->release_sound(this); _manager = 0; @@ -119,7 +118,8 @@ cleanup() { void OpenALAudioSound:: play() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) return; + + if (!is_valid()) return; PN_stdfloat px,py,pz,vx,vy,vz; @@ -136,7 +136,7 @@ play() { } _manager->starting_sound(this); - if (!_source) { + if (!is_playing()) { return; } @@ -195,11 +195,14 @@ play() { void OpenALAudioSound:: stop() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager==0) return; - if (_source) { + if (!is_valid()) return; + + if (is_playing()) { _manager->make_current(); + nassertv(has_sound_data()); + alGetError(); // clear errors alSourceStop(_source); al_audio_errcheck("stopping a source"); @@ -208,15 +211,14 @@ stop() { for (int i=0; i<((int)(_stream_queued.size())); i++) { ALuint buffer = _stream_queued[i]._buffer; if (buffer != _sd->_sample) { - alDeleteBuffers(1, &buffer); - al_audio_errcheck("deleting a buffer"); + _manager->delete_buffer(buffer); } } _stream_queued.resize(0); } _manager->stopping_sound(this); - release_sound_data(); + release_sound_data(false); } /** @@ -225,6 +227,9 @@ stop() { void OpenALAudioSound:: finished() { ReMutexHolder holder(OpenALAudioManager::_lock); + + if (!is_valid()) return; + stop(); _current_time = _length; if (!_finished_event.empty()) { @@ -255,7 +260,8 @@ get_loop() const { void OpenALAudioSound:: set_loop_count(unsigned long loop_count) { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager==0) return; + + if (!is_valid()) return; if (loop_count >= 1000000000) { loop_count = 0; @@ -282,9 +288,14 @@ void OpenALAudioSound:: restart_stalled_audio() { ReMutexHolder holder(OpenALAudioManager::_lock); ALenum status; + + if (!is_valid()) return; + nassertv(is_playing()); + if (_stream_queued.size() == 0) { return; } + alGetError(); alGetSourcei(_source, AL_SOURCE_STATE, &status); if (status != AL_PLAYING) { @@ -298,6 +309,9 @@ restart_stalled_audio() { void OpenALAudioSound:: queue_buffer(ALuint buffer, int samples, int loop_index, double time_offset) { ReMutexHolder holder(OpenALAudioManager::_lock); + + nassertv(is_playing()); + // Now push the buffer into the stream queue. alGetError(); alSourceQueueBuffers(_source,1,&buffer); @@ -322,6 +336,8 @@ ALuint OpenALAudioSound:: make_buffer(int samples, int channels, int rate, unsigned char *data) { ReMutexHolder holder(OpenALAudioManager::_lock); + nassertr(is_playing(), 0); + // Allocate a buffer to hold the data. alGetError(); ALuint buffer; @@ -354,6 +370,8 @@ int OpenALAudioSound:: read_stream_data(int bytelen, unsigned char *buffer) { ReMutexHolder holder(OpenALAudioManager::_lock); + nassertr(has_sound_data(), 0); + MovieAudioCursor *cursor = _sd->_stream; double length = cursor->length(); int channels = cursor->audio_channels(); @@ -404,6 +422,9 @@ read_stream_data(int bytelen, unsigned char *buffer) { void OpenALAudioSound:: correct_calibrated_clock(double rtc, double t) { ReMutexHolder holder(OpenALAudioManager::_lock); + + nassertv(is_playing()); + double cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale; double diff = cc-t; _calibrated_clock_decavg = (_calibrated_clock_decavg * 0.95) + (diff * 0.05); @@ -435,6 +456,11 @@ correct_calibrated_clock(double rtc, double t) { void OpenALAudioSound:: pull_used_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); + + if (!is_valid()) return; + nassertv(is_playing()); + nassertv(has_sound_data()); + while (_stream_queued.size()) { ALuint buffer = 0; ALint num_buffers = 0; @@ -446,18 +472,34 @@ pull_used_buffers() { int err = alGetError(); if (err == AL_NO_ERROR) { if (_stream_queued[0]._buffer != buffer) { - audio_error("corruption in stream queue"); - cleanup(); - return; - } - _stream_queued.pop_front(); - if (_stream_queued.size()) { - double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length; - double rtc = TrueClock::get_global_ptr()->get_short_time(); - correct_calibrated_clock(rtc, al); - } - if (buffer != _sd->_sample) { - alDeleteBuffers(1,&buffer); + // This is certainly atypical: most implementations of OpenAL unqueue + // buffers in FIFO order. However, some (e.g. Apple's) can unqueue + // buffers out-of-order if playback is interrupted. So, we don't freak + // out unless `buffer` isn't in _stream_queued at all. + bool found_culprit = false; + for (auto it = _stream_queued.begin(); it != _stream_queued.end(); ++it) { + if (it->_buffer == buffer) { + // Phew. Found it. Just remove that. + _stream_queued.erase(it); + found_culprit = true; + break; + } + } + if (!found_culprit) { + audio_error("corruption in stream queue"); + cleanup(); + return; + } + } else { + _stream_queued.pop_front(); + if (_stream_queued.size()) { + double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length; + double rtc = TrueClock::get_global_ptr()->get_short_time(); + correct_calibrated_clock(rtc, al); + } + if (buffer != _sd->_sample) { + _manager->delete_buffer(buffer); + } } } else { break; @@ -474,6 +516,10 @@ push_fresh_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); static unsigned char data[65536]; + if (!is_valid()) return; + nassertv(is_playing()); + nassertv(has_sound_data()); + if (_sd->_sample) { while ((_loops_completed < _playing_loops) && (_stream_queued.size() < 100)) { @@ -499,9 +545,9 @@ push_fresh_buffers() { break; } ALuint buffer = make_buffer(samples, channels, rate, data); - if (_manager == 0) return; + if (!is_valid() || !buffer) return; queue_buffer(buffer, samples, loop_index, time_offset); - if (_manager == 0) return; + if (!is_valid()) return; fill += samples; } } @@ -523,7 +569,7 @@ set_time(PN_stdfloat time) { PN_stdfloat OpenALAudioSound:: get_time() const { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) { + if (!is_valid()) { return 0.0; } return _current_time; @@ -535,7 +581,9 @@ get_time() const { void OpenALAudioSound:: cache_time(double rtc) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(_source != 0); + + nassertv(is_playing()); + double t=get_calibrated_clock(rtc); double max = _length * _playing_loops; if (t >= max) { @@ -553,7 +601,7 @@ set_volume(PN_stdfloat volume) { ReMutexHolder holder(OpenALAudioManager::_lock); _volume=volume; - if (_source) { + if (is_playing()) { volume*=_manager->get_volume(); _manager->make_current(); alGetError(); // clear errors @@ -597,7 +645,7 @@ void OpenALAudioSound:: set_play_rate(PN_stdfloat play_rate) { ReMutexHolder holder(OpenALAudioManager::_lock); _play_rate = play_rate; - if (_source) { + if (is_playing()) { alSourcef(_source, AL_PITCH, play_rate); } } @@ -639,7 +687,7 @@ set_3d_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx _velocity[1] = vz; _velocity[2] = -vy; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -675,7 +723,7 @@ set_3d_min_distance(PN_stdfloat dist) { ReMutexHolder holder(OpenALAudioManager::_lock); _min_dist = dist; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -700,7 +748,7 @@ set_3d_max_distance(PN_stdfloat dist) { ReMutexHolder holder(OpenALAudioManager::_lock); _max_dist = dist; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -725,7 +773,7 @@ set_3d_drop_off_factor(PN_stdfloat factor) { ReMutexHolder holder(OpenALAudioManager::_lock); _drop_off_factor = factor; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -743,13 +791,16 @@ get_3d_drop_off_factor() const { } /** - * Sets whether the sound is marked "active". By default, the active flag + * Sets whether the sound is marked "active". By default, the active flag is * true for all sounds. If the active flag is set to false for any particular * sound, the sound will not be heard. */ void OpenALAudioSound:: set_active(bool active) { ReMutexHolder holder(OpenALAudioManager::_lock); + + if (!is_valid()) return; + if (_active!=active) { _active=active; if (_active) { @@ -813,7 +864,7 @@ get_name() const { AudioSound::SoundStatus OpenALAudioSound:: status() const { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_source==0) { + if (!is_playing()) { return AudioSound::READY; } if ((_loops_completed >= _playing_loops)&&(_stream_queued.size()==0)) { diff --git a/panda/src/audiotraits/openalAudioSound.h b/panda/src/audiotraits/openalAudioSound.h index f5b02667e5..e10f4f179a 100644 --- a/panda/src/audiotraits/openalAudioSound.h +++ b/panda/src/audiotraits/openalAudioSound.h @@ -117,12 +117,14 @@ private: void pull_used_buffers(); void push_fresh_buffers(); INLINE bool require_sound_data(); - INLINE void release_sound_data(); + INLINE void release_sound_data(bool force); + + INLINE bool is_valid() const; + INLINE bool is_playing() const; + INLINE bool has_sound_data() const; private: - void do_stop(); - PT(MovieAudio) _movie; OpenALAudioManager::SoundData *_sd; diff --git a/panda/src/bullet/bulletWorld.cxx b/panda/src/bullet/bulletWorld.cxx index f3d48e3c74..18c23c8896 100644 --- a/panda/src/bullet/bulletWorld.cxx +++ b/panda/src/bullet/bulletWorld.cxx @@ -1104,6 +1104,24 @@ get_group_collision_flag(unsigned int group1, unsigned int group2) const { return _filter_cb2._collide[group1].get_bit(group2); } +/** + * + */ +void BulletWorld:: +set_force_update_all_aabbs(bool force) { + LightMutexHolder holder(get_global_lock()); + _world->setForceUpdateAllAabbs(force); +} + +/** + * + */ +bool BulletWorld:: +get_force_update_all_aabbs() const { + LightMutexHolder holder(get_global_lock()); + return _world->getForceUpdateAllAabbs(); +} + /** * */ diff --git a/panda/src/bullet/bulletWorld.h b/panda/src/bullet/bulletWorld.h index 021b6ee268..36f3723535 100644 --- a/panda/src/bullet/bulletWorld.h +++ b/panda/src/bullet/bulletWorld.h @@ -134,6 +134,9 @@ PUBLISHED: void set_group_collision_flag(unsigned int group1, unsigned int group2, bool enable); bool get_group_collision_flag(unsigned int group1, unsigned int group2) const; + void set_force_update_all_aabbs(bool force); + bool get_force_update_all_aabbs() const; + // Callbacks void set_contact_added_callback(CallbackObject *obj); void clear_contact_added_callback(); @@ -166,6 +169,8 @@ PUBLISHED: MAKE_SEQ_PROPERTY(vehicles, get_num_vehicles, get_vehicle); MAKE_SEQ_PROPERTY(constraints, get_num_constraints, get_constraint); MAKE_SEQ_PROPERTY(manifolds, get_num_manifolds, get_manifold); + MAKE_PROPERTY(force_update_all_aabbs, get_force_update_all_aabbs, + set_force_update_all_aabbs); PUBLISHED: // Deprecated methods, will be removed soon void attach_ghost(BulletGhostNode *node); diff --git a/panda/src/char/character.h b/panda/src/char/character.h index 1903d39ab8..80492df93c 100644 --- a/panda/src/char/character.h +++ b/panda/src/char/character.h @@ -30,7 +30,6 @@ #include "sliderTable.h" class CharacterJointBundle; -class ComputedVertices; /** * An animated character, with skeleton-morph animation and either soft- diff --git a/panda/src/char/characterJoint.cxx b/panda/src/char/characterJoint.cxx index 77b3a99e32..6ce5b0be88 100644 --- a/panda/src/char/characterJoint.cxx +++ b/panda/src/char/characterJoint.cxx @@ -39,7 +39,8 @@ CharacterJoint(const CharacterJoint ©) : MovingPartMatrix(copy), _character(NULL), _net_transform(copy._net_transform), - _initial_net_transform_inverse(copy._initial_net_transform_inverse) + _initial_net_transform_inverse(copy._initial_net_transform_inverse), + _skinning_matrix(copy._skinning_matrix) { // We don't copy the sets of transform nodes. } @@ -60,9 +61,12 @@ CharacterJoint(Character *character, // update_internals() to get our _net_transform set properly. update_internals(root, parent, true, false, current_thread); - // And then compute its inverse. This is needed for ComputedVertices, - // during animation. + // And then compute its inverse. This is needed to track changes in + // _net_transform as the joint moves, so we can recompute _skinning_matrix, + // which maps vertices from their initial positions to their animated + // positions. _initial_net_transform_inverse = invert(_net_transform); + _skinning_matrix = LMatrix4::ident_mat(); } /** @@ -141,11 +145,13 @@ update_internals(PartBundle *root, PartGroup *parent, bool self_changed, } } - // Also tell our related JointVertexTransforms that they now need to - // recompute themselves. + // Recompute the transform used by any vertices animated by this joint. + _skinning_matrix = _initial_net_transform_inverse * _net_transform; + + // Also tell our related JointVertexTransforms that we've changed their + // underlying matrix. VertexTransforms::iterator vti; for (vti = _vertex_transforms.begin(); vti != _vertex_transforms.end(); ++vti) { - (*vti)->_matrix_stale = true; (*vti)->mark_modified(current_thread); } } diff --git a/panda/src/char/characterJoint.h b/panda/src/char/characterJoint.h index 759d175c7e..5da83def66 100644 --- a/panda/src/char/characterJoint.h +++ b/panda/src/char/characterJoint.h @@ -108,6 +108,12 @@ public: LMatrix4 _net_transform; LMatrix4 _initial_net_transform_inverse; + // This is the product of the above; the matrix that gets applied to a + // vertex (whose coordinates are in the coordinate space of the character + // in its neutral pose) to transform it from its neutral position to its + // animated position. + LMatrix4 _skinning_matrix; + public: virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/char/jointVertexTransform.I b/panda/src/char/jointVertexTransform.I index a5a2a2e8cf..4973ae6d60 100644 --- a/panda/src/char/jointVertexTransform.I +++ b/panda/src/char/jointVertexTransform.I @@ -18,13 +18,3 @@ INLINE const CharacterJoint *JointVertexTransform:: get_joint() const { return _joint; } - -/** - * Recomputes _matrix if it needs it. - */ -INLINE void JointVertexTransform:: -check_matrix() const { - if (_matrix_stale) { - ((JointVertexTransform *)this)->compute_matrix(); - } -} diff --git a/panda/src/char/jointVertexTransform.cxx b/panda/src/char/jointVertexTransform.cxx index cf943669a7..5212aa6cd9 100644 --- a/panda/src/char/jointVertexTransform.cxx +++ b/panda/src/char/jointVertexTransform.cxx @@ -24,8 +24,7 @@ TypeHandle JointVertexTransform::_type_handle; * Constructs an invalid object; used only by the bam loader. */ JointVertexTransform:: -JointVertexTransform() : - _matrix_stale(true) +JointVertexTransform() { } @@ -35,8 +34,7 @@ JointVertexTransform() : */ JointVertexTransform:: JointVertexTransform(CharacterJoint *joint) : - _joint(joint), - _matrix_stale(true) + _joint(joint) { // Tell the joint that we need to be informed when it moves. _joint->_vertex_transforms.insert(this); @@ -57,8 +55,7 @@ JointVertexTransform:: */ void JointVertexTransform:: get_matrix(LMatrix4 &matrix) const { - check_matrix(); - matrix = _matrix; + matrix = _joint->_skinning_matrix; } /** @@ -69,8 +66,7 @@ get_matrix(LMatrix4 &matrix) const { */ void JointVertexTransform:: mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const { - check_matrix(); - result.multiply(_matrix, previous); + result.multiply(_joint->_skinning_matrix, previous); } /** @@ -80,9 +76,7 @@ mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const { */ void JointVertexTransform:: accumulate_matrix(LMatrix4 &accum, PN_stdfloat weight) const { - check_matrix(); - - accum.accumulate(_matrix, weight); + accum.accumulate(_joint->_skinning_matrix, weight); } /** @@ -93,19 +87,6 @@ output(ostream &out) const { out << _joint->get_name(); } -/** - * Recomputes _matrix if it needs it. Uses locking. - */ -void JointVertexTransform:: -compute_matrix() { - LightMutexHolder holder(_lock); - if (_matrix_stale) { - _matrix = _joint->_initial_net_transform_inverse * _joint->_net_transform; - _matrix_stale = false; - } -} - - /** * Tells the BamReader how to create objects of type JointVertexTransform. */ @@ -165,6 +146,5 @@ fillin(DatagramIterator &scan, BamReader *manager) { VertexTransform::fillin(scan, manager); manager->read_pointer(scan); - _matrix_stale = true; mark_modified(Thread::get_current_thread()); } diff --git a/panda/src/char/jointVertexTransform.h b/panda/src/char/jointVertexTransform.h index 39b531de8d..1be097eba6 100644 --- a/panda/src/char/jointVertexTransform.h +++ b/panda/src/char/jointVertexTransform.h @@ -47,15 +47,8 @@ PUBLISHED: virtual void output(ostream &out) const; private: - INLINE void check_matrix() const; - void compute_matrix(); - PT(CharacterJoint) _joint; - LMatrix4 _matrix; - bool _matrix_stale; - LightMutex _lock; - public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); diff --git a/panda/src/event/asyncFuture.cxx b/panda/src/event/asyncFuture.cxx index f1aaaba842..25ae6bfa1c 100644 --- a/panda/src/event/asyncFuture.cxx +++ b/panda/src/event/asyncFuture.cxx @@ -244,7 +244,6 @@ add_waiting_task(AsyncTask *task) { */ void AsyncFuture:: wake_task(AsyncTask *task) { - cerr << "waking task\n"; AsyncTaskManager *manager = task->_manager; if (manager == nullptr) { // If it's an unscheduled task, schedule it on the same manager as the diff --git a/panda/src/pgraph/clipPlaneAttrib.cxx b/panda/src/pgraph/clipPlaneAttrib.cxx index 2b45be735a..726b5472b7 100644 --- a/panda/src/pgraph/clipPlaneAttrib.cxx +++ b/panda/src/pgraph/clipPlaneAttrib.cxx @@ -900,12 +900,52 @@ write_datagram(BamWriter *manager, Datagram &dg) { int ClipPlaneAttrib:: complete_pointers(TypedWritable **p_list, BamReader *manager) { int pi = RenderAttrib::complete_pointers(p_list, manager); - AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); if (manager->get_file_minor_ver() >= 40) { for (size_t i = 0; i < _off_planes.size(); ++i) { pi += _off_planes[i].complete_pointers(p_list + pi, manager); + } + for (size_t i = 0; i < _on_planes.size(); ++i) { + pi += _on_planes[i].complete_pointers(p_list + pi, manager); + } + + } else { + BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes"); + nassertr(aux != NULL, pi); + + int i; + aux->_off_list.reserve(aux->_num_off_planes); + for (i = 0; i < aux->_num_off_planes; ++i) { + PandaNode *node; + DCAST_INTO_R(node, p_list[pi++], pi); + aux->_off_list.push_back(node); + } + + aux->_on_list.reserve(aux->_num_on_planes); + for (i = 0; i < aux->_num_on_planes; ++i) { + PandaNode *node; + DCAST_INTO_R(node, p_list[pi++], pi); + aux->_on_list.push_back(node); + } + } + + return pi; +} + +/** + * Called by the BamReader to perform any final actions needed for setting up + * the object after all objects have been read and all pointers have been + * completed. + */ +void ClipPlaneAttrib:: +finalize(BamReader *manager) { + if (manager->get_file_minor_ver() >= 40) { + AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); + + // Check if any of the nodes we loaded are mentioned in the + // AttribNodeRegistry. If so, replace them. + for (size_t i = 0; i < _off_planes.size(); ++i) { int n = areg->find_node(_off_planes[i]); if (n != -1) { // If it's in the registry, replace it. @@ -914,8 +954,6 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) { } for (size_t i = 0; i < _on_planes.size(); ++i) { - pi += _on_planes[i].complete_pointers(p_list + pi, manager); - int n = areg->find_node(_on_planes[i]); if (n != -1) { // If it's in the registry, replace it. @@ -924,53 +962,46 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) { } } else { - Planes::iterator ci = _off_planes.begin(); - while (ci != _off_planes.end()) { - PandaNode *node; - DCAST_INTO_R(node, p_list[pi++], pi); + // Now it's safe to convert our saved PandaNodes into NodePaths. + BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes"); + nassertv(aux != NULL); + nassertv(aux->_num_off_planes == (int)aux->_off_list.size()); + nassertv(aux->_num_on_planes == (int)aux->_on_list.size()); - // We go through some effort to look up the node in the registry without - // creating a NodePath around it first (which would up, and then down, - // the reference count, possibly deleting the node). - int ni = areg->find_node(node->get_type(), node->get_name()); - if (ni != -1) { - (*ci) = areg->get_node(ni); + AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); + + _off_planes.reserve(aux->_off_list.size()); + NodeList::iterator ni; + for (ni = aux->_off_list.begin(); ni != aux->_off_list.end(); ++ni) { + PandaNode *node = (*ni); + int n = areg->find_node(node->get_type(), node->get_name()); + if (n != -1) { + // If it's in the registry, add that NodePath. + _off_planes.push_back(areg->get_node(n)); } else { - (*ci) = NodePath(node); + // Otherwise, add any arbitrary NodePath. Complain if it's ambiguous. + _off_planes.push_back(NodePath(node)); } - ++ci; } - ci = _on_planes.begin(); - while (ci != _on_planes.end()) { - PandaNode *node; - DCAST_INTO_R(node, p_list[pi++], pi); - - int ni = areg->find_node(node->get_type(), node->get_name()); - if (ni != -1) { - (*ci) = areg->get_node(ni); + _on_planes.reserve(aux->_on_list.size()); + for (ni = aux->_on_list.begin(); ni != aux->_on_list.end(); ++ni) { + PandaNode *node = (*ni); + int n = areg->find_node(node->get_type(), node->get_name()); + if (n != -1) { + // If it's in the registry, add that NodePath. + _on_planes.push_back(areg->get_node(n)); + node = _on_planes.back().node(); } else { - (*ci) = NodePath(node); + // Otherwise, add any arbitrary NodePath. Complain if it's ambiguous. + _on_planes.push_back(NodePath(node)); } - ++ci; } } + // Now that the NodePaths have been filled in, we can sort the list. _off_planes.sort(); _on_planes.sort(); - - return pi; -} - -/** - * Some objects require all of their nested pointers to have been completed - * before the objects themselves can be completed. If this is the case, - * override this method to return true, and be careful with circular - * references (which would make the object unreadable from a bam file). - */ -bool ClipPlaneAttrib:: -require_fully_complete() const { - return true; } /** @@ -987,6 +1018,8 @@ make_from_bam(const FactoryParams ¶ms) { parse_params(params, scan, manager); attrib->fillin(scan, manager); + manager->register_finalize(attrib); + return attrib; } @@ -1000,26 +1033,24 @@ fillin(DatagramIterator &scan, BamReader *manager) { _off_all_planes = scan.get_bool(); - int num_off_planes = scan.get_uint16(); - - // Push back an empty NodePath for each off Plane for now, until we get the - // actual list of pointers later in complete_pointers(). - _off_planes.resize(num_off_planes); if (manager->get_file_minor_ver() >= 40) { - for (int i = 0; i < num_off_planes; i++) { + _off_planes.resize(scan.get_uint16()); + for (size_t i = 0; i < _off_planes.size(); ++i) { _off_planes[i].fillin(scan, manager); } - } else { - manager->read_pointers(scan, num_off_planes); - } - int num_on_planes = scan.get_uint16(); - _on_planes.resize(num_on_planes); - if (manager->get_file_minor_ver() >= 40) { - for (int i = 0; i < num_on_planes; i++) { - manager->read_pointer(scan); + _on_planes.resize(scan.get_uint16()); + for (size_t i = 0; i < _on_planes.size(); ++i) { + _on_planes[i].fillin(scan, manager); } } else { - manager->read_pointers(scan, num_on_planes); + BamAuxData *aux = new BamAuxData; + manager->set_aux_data(this, "planes", aux); + + aux->_num_off_planes = scan.get_uint16(); + manager->read_pointers(scan, aux->_num_off_planes); + + aux->_num_on_planes = scan.get_uint16(); + manager->read_pointers(scan, aux->_num_on_planes); } } diff --git a/panda/src/pgraph/clipPlaneAttrib.h b/panda/src/pgraph/clipPlaneAttrib.h index ca95233b79..b59bb34f9b 100644 --- a/panda/src/pgraph/clipPlaneAttrib.h +++ b/panda/src/pgraph/clipPlaneAttrib.h @@ -125,11 +125,26 @@ PUBLISHED: } MAKE_PROPERTY(class_slot, get_class_slot); +public: + // This data is only needed when reading from a bam file. + typedef pvector NodeList; + class BamAuxData : public BamReader::AuxData { + public: + // We hold a pointer to each of the PandaNodes on the on_list and + // off_list. We will later convert these to NodePaths in + // finalize(). + int _num_off_planes; + int _num_on_planes; + NodeList _off_list; + NodeList _on_list; + }; + public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); virtual int complete_pointers(TypedWritable **plist, BamReader *manager); - virtual bool require_fully_complete() const; + + virtual void finalize(BamReader *manager); protected: static TypedWritable *make_from_bam(const FactoryParams ¶ms); diff --git a/panda/src/pgraph/cullResult.cxx b/panda/src/pgraph/cullResult.cxx index 0118888108..7dbe002c9e 100644 --- a/panda/src/pgraph/cullResult.cxx +++ b/panda/src/pgraph/cullResult.cxx @@ -128,6 +128,29 @@ add_object(CullableObject *object, const CullTraverser *traverser) { object->_state = object->_state->compose(get_rescale_normal_state(mode)); } + // Check for a special wireframe setting. + const RenderModeAttrib *rmode; + if (object->_state->get_attrib(rmode)) { + if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) { + CullableObject *wireframe_part = new CullableObject(*object); + wireframe_part->_state = get_wireframe_overlay_state(rmode); + + if (wireframe_part->munge_geom + (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread), + traverser, force)) { + int wireframe_bin_index = bin_manager->find_bin("fixed"); + CullBin *bin = get_bin(wireframe_bin_index); + nassertv(bin != (CullBin *)NULL); + check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index); + bin->add_object(wireframe_part, current_thread); + } else { + delete wireframe_part; + } + + object->_state = object->_state->compose(get_wireframe_filled_state()); + } + } + // Check to see if there's a special transparency setting. const TransparencyAttrib *trans; if (object->_state->get_attrib(trans)) { @@ -216,29 +239,6 @@ add_object(CullableObject *object, const CullTraverser *traverser) { } } - // Check for a special wireframe setting. - const RenderModeAttrib *rmode; - if (object->_state->get_attrib(rmode)) { - if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) { - CullableObject *wireframe_part = new CullableObject(*object); - wireframe_part->_state = get_wireframe_overlay_state(rmode); - - if (wireframe_part->munge_geom - (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread), - traverser, force)) { - int wireframe_bin_index = bin_manager->find_bin("fixed"); - CullBin *bin = get_bin(wireframe_bin_index); - nassertv(bin != (CullBin *)NULL); - check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index); - bin->add_object(wireframe_part, current_thread); - } else { - delete wireframe_part; - } - - object->_state = object->_state->compose(get_wireframe_filled_state()); - } - } - int bin_index = object->_state->get_bin_index(); CullBin *bin = get_bin(bin_index); nassertv(bin != (CullBin *)NULL); diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index f741b02db8..372bd89e1d 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -344,7 +344,7 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { case TextureStage::M_modulate_gloss: if (shader_attrib->auto_gloss_on()) { - info._flags = ShaderKey::TF_map_glow; + info._flags = ShaderKey::TF_map_gloss; } else { info._mode = TextureStage::M_modulate; info._flags = ShaderKey::TF_has_rgb; diff --git a/panda/src/putil/bamReader.cxx b/panda/src/putil/bamReader.cxx index 30ad07415a..3ab1386d7f 100644 --- a/panda/src/putil/bamReader.cxx +++ b/panda/src/putil/bamReader.cxx @@ -1278,7 +1278,8 @@ p_read_object() { } else { if (bam_cat.is_spam()) { bam_cat.spam() - << "Read a " << object->get_type() << ": " << (void *)object << "\n"; + << "Read a " << object->get_type() << ": " << (void *)object + << " (id=" << object_id << ")\n"; } } } diff --git a/panda/src/vision/config_vision.cxx b/panda/src/vision/config_vision.cxx index 3b53c0ed02..50d4b8aeb3 100644 --- a/panda/src/vision/config_vision.cxx +++ b/panda/src/vision/config_vision.cxx @@ -26,6 +26,10 @@ Configure(config_vision); NotifyCategoryDef(vision, ""); +ConfigVariableBool v4l_blocking +("v4l-blocking", false, + PRC_DESC("Set this to true if you want to block waiting for webcam frames.")); + ConfigureFn(config_vision) { init_libvision(); } diff --git a/panda/src/vision/config_vision.h b/panda/src/vision/config_vision.h index 5eec1a95b2..1ad2aaa59d 100644 --- a/panda/src/vision/config_vision.h +++ b/panda/src/vision/config_vision.h @@ -16,9 +16,12 @@ #include "pandabase.h" #include "notifyCategoryProxy.h" +#include "configVariableBool.h" NotifyCategoryDecl(vision, EXPCL_VISION, EXPTP_VISION); +extern ConfigVariableBool v4l_blocking; + extern EXPCL_VISION void init_libvision(); #endif diff --git a/panda/src/vision/webcamVideoCursorV4L.cxx b/panda/src/vision/webcamVideoCursorV4L.cxx index 64090c53aa..a78c55135a 100644 --- a/panda/src/vision/webcamVideoCursorV4L.cxx +++ b/panda/src/vision/webcamVideoCursorV4L.cxx @@ -209,7 +209,13 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) { _buffers = NULL; _buflens = NULL; - _fd = open(src->_device.c_str(), O_RDWR); + + int mode = O_RDWR; + if (!v4l_blocking) { + mode = O_NONBLOCK; + } + + _fd = open(src->_device.c_str(), mode); if (-1 == _fd) { vision_cat.error() << "Failed to open " << src->_device.c_str() << "\n"; return; @@ -247,6 +253,10 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) { _num_components = 4; break; + case V4L2_PIX_FMT_GREY: + _num_components = 1; + break; + default: vision_cat.error() << "Unsupported pixel format " << src->get_pixel_format() << "!\n"; _ready = false; @@ -393,6 +403,10 @@ fetch_buffer() { vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) { + if (errno == EAGAIN) { + // Simply nothing is available yet. + return NULL; + } vision_cat.error() << "Failed to dequeue buffer!\n"; return NULL; } @@ -484,6 +498,7 @@ fetch_buffer() { case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_GREY: // Simplest case: copying every row verbatim. nassertr(old_bpl == new_bpl, NULL); diff --git a/panda/src/vision/webcamVideoV4L.cxx b/panda/src/vision/webcamVideoV4L.cxx index b07e1b2071..7d5636f4e0 100644 --- a/panda/src/vision/webcamVideoV4L.cxx +++ b/panda/src/vision/webcamVideoV4L.cxx @@ -174,6 +174,7 @@ void find_all_webcams_v4l() { case V4L2_PIX_FMT_BGR32: case V4L2_PIX_FMT_RGB24: case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_GREY: break; default: diff --git a/tests/display/conftest.py b/tests/display/conftest.py index c66ef2f54a..88a2b5f3c3 100644 --- a/tests/display/conftest.py +++ b/tests/display/conftest.py @@ -41,7 +41,9 @@ def window(graphics_pipe, graphics_engine): ) graphics_engine.open_windows() - assert win is not None + if win is None: + pytest.skip("GraphicsPipe cannot make windows") + yield win if win is not None: