From 3ca3dfd13aa04eacbf670c9ffe6093d5a12f177f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 17:10:52 +0200 Subject: [PATCH 01/77] interrogate: fix in-place or (|=) operators (see #588) --- dtool/src/interrogate/interfaceMakerPythonNative.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index c617b6597e..dadd094ca4 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -431,6 +431,12 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap, return true; } + if (method_name == "operator |=") { + def._answer_location = "nb_inplace_or"; + def._wrapper_type = WT_inplace_binary_operator; + return true; + } + if (method_name == "__ipow__") { def._answer_location = "nb_inplace_power"; def._wrapper_type = WT_inplace_ternary_operator; From 98227daaa5c29d7618d3b4e12426ca59e9860867 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 17:11:42 +0200 Subject: [PATCH 02/77] putil: fix SparseArray::clear_range et al Fixes #588 --- panda/src/putil/sparseArray.cxx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/panda/src/putil/sparseArray.cxx b/panda/src/putil/sparseArray.cxx index 6373e53039..5404728aa1 100644 --- a/panda/src/putil/sparseArray.cxx +++ b/panda/src/putil/sparseArray.cxx @@ -262,8 +262,8 @@ compare_to(const SparseArray &other) const { return -1; } - --ai; - --bi; + ++ai; + ++bi; } if (ai != _subranges.rend()) { @@ -440,9 +440,9 @@ do_remove_range(int begin, int end) { if (si == _subranges.end()) { if (!_subranges.empty()) { si = _subranges.begin() + _subranges.size() - 1; - if ((*si)._end >= begin) { + if ((*si)._end > begin) { // The new range shortens the last element of the array on the right. - end = std::min(end, (*si)._begin); + end = std::max(begin, (*si)._begin); (*si)._end = end; // It might also shorten it on the left; fall through. } else { @@ -462,10 +462,10 @@ do_remove_range(int begin, int end) { if (si != _subranges.begin()) { Subranges::iterator si2 = si; --si2; - if ((*si2)._end >= begin) { + if ((*si2)._end > begin) { // The new range shortens an element within the array on the right // (but does not intersect the next element). - end = std::min(end, (*si2)._begin); + end = std::max(begin, (*si2)._begin); (*si2)._end = end; // It might also shorten it on the left; fall through. si = si2; @@ -488,7 +488,7 @@ do_remove_range(int begin, int end) { } // Check if the new range removes any elements to the left. - while (begin <= (*si)._begin) { + while (begin <= (*si)._begin || (*si)._begin >= (*si)._end) { if (si == _subranges.begin()) { _subranges.erase(si); return; @@ -500,6 +500,7 @@ do_remove_range(int begin, int end) { } (*si)._end = std::min((*si)._end, begin); + nassertv((*si)._end > (*si)._begin); } /** From 83723d38a5e68a2b3af35d9e0958d71781f2d8d6 Mon Sep 17 00:00:00 2001 From: Epihaius Date: Mon, 25 Mar 2019 15:15:06 +0100 Subject: [PATCH 03/77] tests: Create test_sparsearray.py Closes #590 --- tests/putil/test_sparsearray.py | 219 ++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 tests/putil/test_sparsearray.py diff --git a/tests/putil/test_sparsearray.py b/tests/putil/test_sparsearray.py new file mode 100644 index 0000000000..ae6d5208b5 --- /dev/null +++ b/tests/putil/test_sparsearray.py @@ -0,0 +1,219 @@ +from panda3d import core + + +def test_sparse_array_set_bit_to(): + """Tests SparseArray behavior for set_bit_to().""" + + s = core.SparseArray() + s.set_bit_to(5, True) + assert s.get_bit(5) + + s = core.SparseArray.all_on() + s.set_bit_to(5, False) + assert not s.get_bit(5) + + +def test_sparse_array_clear(): + """Tests SparseArray behavior for clear().""" + + s = core.SparseArray.all_on() + s.clear() + assert s.is_zero() + assert not s.is_inverse() + assert s.get_num_subranges() == 0 + assert s.get_num_on_bits() == 0 + assert s.get_num_bits() == 0 + + s = core.SparseArray() + s.set_range(5, 10) + s.clear() + assert s.is_zero() + assert not s.is_inverse() + assert s.get_num_subranges() == 0 + assert s.get_num_on_bits() == 0 + assert s.get_num_bits() == 0 + + +def test_sparse_array_clear_range(): + """Tests SparseArray behavior for clear_range().""" + + # test clear_range with single overlapping on-range + # (clear_range extends beyond highest on-bit) + s = core.SparseArray() + s.set_range(2, 3) + s.clear_range(3, 3) + assert s.get_bit(2) + assert not s.get_bit(3) + + # same as above, using set_range_to + s = core.SparseArray() + s.set_range_to(True, 2, 3) + s.set_range_to(False, 3, 3) + assert s.get_bit(2) + assert not s.get_bit(3) + + # test clear_range using off-range which overlaps two on-ranges + # (lowest off-bit in lowest on-range, highest off-bit in highest on-range) + s = core.SparseArray() + s.set_range(2, 3) + s.set_range(7, 3) + s.clear_range(3, 6) + assert s.get_bit(2) + assert not s.get_bit(3) + assert not s.get_bit(8) + assert s.get_bit(9) + + # same as above, using set_range_to + s = core.SparseArray() + s.set_range_to(True, 2, 3) + s.set_range_to(True, 7, 3) + s.set_range_to(False, 3, 6) + assert s.get_bit(2) + assert not s.get_bit(3) + assert not s.get_bit(8) + assert s.get_bit(9) + + +def test_sparse_array_set_range(): + """Tests SparseArray behavior for set_range().""" + + # test set_range with single overlapping off-range + # (set_range extends beyond highest off-bit) + s = core.SparseArray.all_on() + s.clear_range(2, 3) + s.set_range(3, 3) + assert not s.get_bit(2) + assert s.get_bit(3) + + # same as above, using set_range_to + s = core.SparseArray.all_on() + s.set_range_to(False, 2, 3) + s.set_range_to(True, 3, 3) + assert not s.get_bit(2) + assert s.get_bit(3) + + # test set_range using on-range which overlaps two off-ranges + # (lowest on-bit in lowest off-range, highest on-bit in highest off-range) + s = core.SparseArray.all_on() + s.clear_range(2, 3) + s.clear_range(7, 3) + s.set_range(3, 6) + assert not s.get_bit(2) + assert s.get_bit(3) + assert s.get_bit(8) + assert not s.get_bit(9) + + # same as above, using set_range_to + s = core.SparseArray.all_on() + s.set_range_to(False, 2, 3) + s.set_range_to(False, 7, 3) + s.set_range_to(True, 3, 6) + assert not s.get_bit(2) + assert s.get_bit(3) + assert s.get_bit(8) + assert not s.get_bit(9) + + +def test_sparse_array_bits_in_common(): + """Tests SparseArray behavior for has_bits_in_common().""" + + s = core.SparseArray() + t = core.SparseArray() + s.set_range(2, 4) + t.set_range(5, 4) + assert s.has_bits_in_common(t) + + s = core.SparseArray() + t = core.SparseArray() + s.set_range(2, 4) + t.set_range(6, 4) + assert not s.has_bits_in_common(t) + + +def test_sparse_array_operations(): + """Tests SparseArray behavior for various operations.""" + + # test bitshift to left + s = core.SparseArray() + s.set_bit(2) + t = s << 2 + assert t.get_bit(4) + assert not t.get_bit(2) + + # test bitshift to right + s = core.SparseArray() + s.set_bit(4) + t = s >> 2 + assert t.get_bit(2) + assert not t.get_bit(4) + + # test bitwise AND + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s & t + assert not u.get_bit(0) + assert not u.get_bit(1) + assert not u.get_bit(2) + assert u.get_bit(3) + + # test bitwise OR + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s | t + assert not u.get_bit(0) + assert u.get_bit(1) + assert u.get_bit(2) + assert u.get_bit(3) + + # test bitwise XOR + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s ^ t + assert not u.get_bit(0) + assert u.get_bit(1) + assert u.get_bit(2) + assert not u.get_bit(3) + + +def test_sparse_array_augm_assignment(): + """Tests SparseArray behavior for augmented assignments.""" + + # test in-place bitshift to left + s = t = core.SparseArray() + t <<= 2 + assert s is t + + # test in-place bitshift to right + s = t = core.SparseArray() + t >>= 2 + assert s is t + + # test in-place bitwise AND + s = t = core.SparseArray() + u = core.SparseArray() + t &= u + assert s is t + + # test in-place bitwise OR + s = t = core.SparseArray() + u = core.SparseArray() + t |= u + assert s is t + + # test in-place bitwise XOR + s = t = core.SparseArray() + u = core.SparseArray() + t ^= u + assert s is t From 6464327e6f41282109c671b6b5159fc2a2b5e5c7 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 17:59:55 +0200 Subject: [PATCH 04/77] tests: add more thorough unit test for SparseArray::clear_range --- tests/putil/test_sparsearray.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/putil/test_sparsearray.py b/tests/putil/test_sparsearray.py index ae6d5208b5..b7767d6e8a 100644 --- a/tests/putil/test_sparsearray.py +++ b/tests/putil/test_sparsearray.py @@ -35,7 +35,22 @@ def test_sparse_array_clear(): def test_sparse_array_clear_range(): - """Tests SparseArray behavior for clear_range().""" + # Not using parametrize because there are too many values for that. + for mask in range(0x7f): + for begin in range(8): + for size in range(8): + b = core.BitArray(mask) + s = core.SparseArray(b) + + s.clear_range(begin, size) + b.clear_range(begin, size) + + assert core.BitArray(s) == b + assert s == core.SparseArray(b) + + +def test_sparse_array_set_clear_ranges(): + """Tests SparseArray behavior for setting and clearing ranges.""" # test clear_range with single overlapping on-range # (clear_range extends beyond highest on-bit) From 5d3499dc6483bcf5c3069f3b92240143cad1e250 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 18:01:23 +0200 Subject: [PATCH 05/77] x11display: fix crash when starting in fullscreen on Linux/X11 Fixes #618 --- panda/src/x11display/x11GraphicsWindow.cxx | 38 +++++----------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 5599959cdb..0a5d969bdf 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -589,7 +589,13 @@ set_properties_now(WindowProperties &properties) { // OK, first figure out which CRTC the window is on. It may be on more // than one, actually, so grab a point in the center in order to figure // out which one it's more-or-less mostly on. - LPoint2i center = _properties.get_origin() + _properties.get_size() / 2; + LPoint2i center(0, 0); + if (_properties.has_origin()) { + center = _properties.get_origin(); + if (_properties.has_size()) { + center += _properties.get_size() / 2; + } + } int x, y, width, height; x11_pipe->find_fullscreen_crtc(center, x, y, width, height); @@ -628,7 +634,7 @@ set_properties_now(WindowProperties &properties) { // We may need to change the screen resolution. The code below is // suboptimal; in the future, we probably want to only touch the CRTC // that the window is on. - XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow); + XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root()); SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation); SizeID new_size_id = (SizeID) -1; int num_sizes = 0; @@ -1010,34 +1016,6 @@ open_window() { // Make sure we are not making X11 calls from other threads. LightReMutexHolder holder(x11GraphicsPipe::_x_mutex); - if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) { - XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root()); - if (_orig_size_id == (SizeID) -1) { - _orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation); - } - int num_sizes, new_size_id = -1; - XRRScreenSize *xrrs; - xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes); - for (int i = 0; i < num_sizes; ++i) { - if (xrrs[i].width == _properties.get_x_size() && - xrrs[i].height == _properties.get_y_size()) { - new_size_id = i; - } - } - if (new_size_id == -1) { - x11display_cat.error() - << "Videocard has no supported display resolutions at specified res (" - << _properties.get_x_size() << " x " << _properties.get_y_size() <<")\n"; - _orig_size_id = -1; - return false; - } - if (new_size_id != _orig_size_id) { - _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime); - } else { - _orig_size_id = -1; - } - } - X11_Window parent_window = x11_pipe->get_root(); WindowHandle *window_handle = _properties.get_parent_window(); if (window_handle != nullptr) { From 5530074945d6b94f02b5a89edfc835009abcb726 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 22:10:32 +0200 Subject: [PATCH 06/77] cocoa: fix crash when typing with RIME as input method Fixes #620 --- panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 0a140109c7..42490b43da 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -1618,7 +1618,7 @@ handle_key_event(NSEvent *event) { if ([event type] == NSKeyDown) { // Translate it to a unicode character for keystrokes. I would use // interpretKeyEvents and insertText, but that doesn't handle dead keys. - TISInputSourceRef input_source = TISCopyCurrentKeyboardInputSource(); + TISInputSourceRef input_source = TISCopyCurrentKeyboardLayoutInputSource(); CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData); const UCKeyboardLayout *layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); @@ -1827,7 +1827,7 @@ get_keyboard_map() const { const UCKeyboardLayout *layout; // Get the current keyboard layout data. - input_source = TISCopyCurrentKeyboardInputSource(); + input_source = TISCopyCurrentKeyboardLayoutInputSource(); layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData); layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); From 239dc400325fa530316fbc40a60987eca353ff89 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 22:11:20 +0200 Subject: [PATCH 07/77] device: fix crash when unplugging certain devices on macOS Fixes #621 --- panda/src/device/ioKitInputDevice.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index da1369932e..8468fd4816 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -22,8 +22,11 @@ #include "mouseButton.h" static void removal_callback(void *ctx, IOReturn result, void *sender) { - IOKitInputDevice *input_device = (IOKitInputDevice *)ctx; + // We need to hold a reference to this because it may otherwise be destroyed + // during the call to on_remove(). + PT(IOKitInputDevice) input_device = (IOKitInputDevice *)ctx; nassertv(input_device != nullptr); + nassertv(input_device->test_ref_count_integrity()); input_device->on_remove(); } From 6df700939a5c5af728dea2479df70be644ebe1c9 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 Apr 2019 23:08:02 +0200 Subject: [PATCH 08/77] glsl: make ParamVecBase4 and ParamVecBase4i work with ptr inputs --- panda/src/display/graphicsStateGuardian.cxx | 8 ++++ panda/src/display/graphicsStateGuardian.h | 1 + panda/src/glstuff/glShaderContext_src.cxx | 40 ++++++++-------- panda/src/pgraph/shaderAttrib.cxx | 51 +++++++++++++++++++++ panda/src/pgraph/shaderAttrib.h | 1 + tests/display/test_glsl_shader.py | 30 ++++++++++++ 6 files changed, 111 insertions(+), 20 deletions(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 749fbc7b35..851ca112ea 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -1901,6 +1901,14 @@ fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) { return (_target_shader->get_shader_input_ptr(spec._arg)); } +/** + * + */ +bool GraphicsStateGuardian:: +fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec, Shader::ShaderPtrData &data) { + return _target_shader->get_shader_input_ptr(spec._arg, data); +} + /** * Makes the specified DisplayRegion current. All future drawing and clear * operations will be constrained within the given DisplayRegion. diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h index e1c9435fef..cb556499b2 100644 --- a/panda/src/display/graphicsStateGuardian.h +++ b/panda/src/display/graphicsStateGuardian.h @@ -339,6 +339,7 @@ public: PT(Texture) fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, int &view); const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec); + bool fetch_ptr_parameter(const Shader::ShaderPtrSpec &spec, Shader::ShaderPtrData &data); virtual void prepare_display_region(DisplayRegionPipelineReader *dr); virtual void clear_before_callback(); diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index 5a7eda65fc..8789f26650 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -2110,8 +2110,8 @@ issue_parameters(int altered) { for (int i = 0; i < (int)_shader->_ptr_spec.size(); ++i) { Shader::ShaderPtrSpec &spec = _shader->_ptr_spec[i]; - const Shader::ShaderPtrData* ptr_data = _glgsg->fetch_ptr_parameter(spec); - if (ptr_data == nullptr) { //the input is not contained in ShaderPtrData + Shader::ShaderPtrData ptr_data; + if (!_glgsg->fetch_ptr_parameter(spec, ptr_data)) { //the input is not contained in ShaderPtrData release_resources(); return; } @@ -2119,18 +2119,18 @@ issue_parameters(int altered) { nassertd(spec._dim[1] > 0) continue; GLint p = spec._id._seqno; - int array_size = min(spec._dim[0], (int)ptr_data->_size / spec._dim[1]); + int array_size = min(spec._dim[0], (int)ptr_data._size / spec._dim[1]); switch (spec._type) { case Shader::SPT_float: { float *data = nullptr; - switch (ptr_data->_type) { + switch (ptr_data._type) { case Shader::SPT_int: // Convert int data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((int*)ptr_data->_ptr)[i]); + data[i] = (float)(((int*)ptr_data._ptr)[i]); } break; @@ -2138,7 +2138,7 @@ issue_parameters(int altered) { // Convert unsigned int data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((unsigned int*)ptr_data->_ptr)[i]); + data[i] = (float)(((unsigned int*)ptr_data._ptr)[i]); } break; @@ -2146,12 +2146,12 @@ issue_parameters(int altered) { // Downgrade double data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((double*)ptr_data->_ptr)[i]); + data[i] = (float)(((double*)ptr_data._ptr)[i]); } break; case Shader::SPT_float: - data = (float*)ptr_data->_ptr; + data = (float*)ptr_data._ptr; break; default: @@ -2171,8 +2171,8 @@ issue_parameters(int altered) { break; case Shader::SPT_int: - if (ptr_data->_type != Shader::SPT_int && - ptr_data->_type != Shader::SPT_uint) { + if (ptr_data._type != Shader::SPT_int && + ptr_data._type != Shader::SPT_uint) { GLCAT.error() << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n"; @@ -2183,18 +2183,18 @@ issue_parameters(int altered) { } else { switch (spec._dim[1]) { - case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data->_ptr); continue; + case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data._ptr); continue; + case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data._ptr); continue; + case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data._ptr); continue; + case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data._ptr); continue; } nassertd(false) continue; } break; case Shader::SPT_uint: - if (ptr_data->_type != Shader::SPT_uint && - ptr_data->_type != Shader::SPT_int) { + if (ptr_data._type != Shader::SPT_uint && + ptr_data._type != Shader::SPT_int) { GLCAT.error() << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n"; @@ -2205,10 +2205,10 @@ issue_parameters(int altered) { } else { switch (spec._dim[1]) { - case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; + case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; } nassertd(false) continue; } diff --git a/panda/src/pgraph/shaderAttrib.cxx b/panda/src/pgraph/shaderAttrib.cxx index 24b245b218..934773ad49 100644 --- a/panda/src/pgraph/shaderAttrib.cxx +++ b/panda/src/pgraph/shaderAttrib.cxx @@ -422,6 +422,57 @@ get_shader_input_ptr(const InternalName *id) const { } } +/** + * Returns the ShaderInput as a ShaderPtrData struct. Assertion fails if + * there is none. or if it is not a PTA(double/float) + */ +bool ShaderAttrib:: +get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const { + Inputs::const_iterator i = _inputs.find(id); + if (i != _inputs.end()) { + const ShaderInput &p = (*i).second; + if (p.get_value_type() == ShaderInput::M_numeric || + p.get_value_type() == ShaderInput::M_vector) { + + data = p.get_ptr(); + return (data._ptr != nullptr); + } + if (p.get_value_type() == ShaderInput::M_param) { + // Temporary solution until the new param system + TypedWritableReferenceCount *param = p.get_value(); + if (param != nullptr) { + if (param->is_of_type(ParamVecBase4f::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4f *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_float; + return true; + } + else if (param->is_of_type(ParamVecBase4i::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4i *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_int; + return true; + } + else if (param->is_of_type(ParamVecBase4d::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4d *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_float; + return true; + } + } + } + ostringstream strm; + strm << "Shader input " << id->get_name() << " was given an incompatible parameter type.\n"; + nassert_raise(strm.str()); + return false; + } else { + ostringstream strm; + strm << "Shader input " << id->get_name() << " is not present.\n"; + nassert_raise(strm.str()); + return false; + } +} + /** * Returns the ShaderInput as a texture. Assertion fails if there is none, or * if it is not a texture. diff --git a/panda/src/pgraph/shaderAttrib.h b/panda/src/pgraph/shaderAttrib.h index 29b535ff8a..fb0cc6e996 100644 --- a/panda/src/pgraph/shaderAttrib.h +++ b/panda/src/pgraph/shaderAttrib.h @@ -119,6 +119,7 @@ PUBLISHED: LVecBase4 get_shader_input_vector(InternalName *id) const; Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=nullptr) const; const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const; + bool get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const; const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const; ShaderBuffer *get_shader_input_buffer(const InternalName *id) const; diff --git a/tests/display/test_glsl_shader.py b/tests/display/test_glsl_shader.py index 8deadf40d9..401166a26d 100644 --- a/tests/display/test_glsl_shader.py +++ b/tests/display/test_glsl_shader.py @@ -294,6 +294,36 @@ def test_glsl_pta_mat4(gsg): run_glsl_test(gsg, code, preamble, {'pta': pta}) +def test_glsl_param_vec4(gsg): + param = core.ParamVecBase4((0, 1, 2, 3)) + + preamble = """ + uniform vec4 param; + """ + code = """ + assert(param.x == 0.0); + assert(param.y == 1.0); + assert(param.z == 2.0); + assert(param.w == 3.0); + """ + run_glsl_test(gsg, code, preamble, {'param': param}) + + +def test_glsl_param_ivec4(gsg): + param = core.ParamVecBase4i((0, 1, 2, 3)) + + preamble = """ + uniform ivec4 param; + """ + code = """ + assert(param.x == 0); + assert(param.y == 1); + assert(param.z == 2); + assert(param.w == 3); + """ + run_glsl_test(gsg, code, preamble, {'param': param}) + + def test_glsl_write_extract_image_buffer(gsg): # Tests that we can write to a buffer texture on the GPU, and then extract # the data on the CPU. We test two textures since there was in the past a From 2288ffca8b45839e26f87ea46472e76b15d8742b Mon Sep 17 00:00:00 2001 From: hecris Date: Sat, 13 Apr 2019 02:15:51 -0400 Subject: [PATCH 09/77] tests: add bullet heightfield test Closes #619 --- tests/bullet/test_bullet_heightfield.py | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/bullet/test_bullet_heightfield.py diff --git a/tests/bullet/test_bullet_heightfield.py b/tests/bullet/test_bullet_heightfield.py new file mode 100644 index 0000000000..28174b93ec --- /dev/null +++ b/tests/bullet/test_bullet_heightfield.py @@ -0,0 +1,45 @@ +import pytest +# Skip these tests if we can't import bullet. +bullet = pytest.importorskip("panda3d.bullet") + +from panda3d.bullet import BulletWorld, BulletRigidBodyNode, ZUp +from panda3d.bullet import BulletHeightfieldShape, BulletSphereShape +from panda3d.core import NodePath, PNMImage + + +def make_node(name, BulletShape, *args): + # Returns a BulletRigidBodyNode for the given shape + shape = BulletShape(*args) + node = BulletRigidBodyNode(name) + node.add_shape(shape) + return node + + +def test_sphere_into_heightfield(): + root = NodePath("root") + world = BulletWorld() + # Create PNMImage to construct Heightfield with + img = PNMImage(10, 10, 1) + img.fill_val(255) + # Make our nodes + heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp) + sphere = make_node("Sphere", BulletSphereShape, 1) + # Attach to world + np1 = root.attach_new_node(sphere) + np1.set_pos(0, 0, 1) + world.attach(sphere) + + np2 = root.attach_new_node(heightfield) + np2.set_pos(0, 0, 0) + world.attach(heightfield) + + assert world.get_num_rigid_bodies() == 2 + test = world.contact_test_pair(sphere, heightfield) + assert test.get_num_contacts() > 0 + assert test.get_contact(0).get_node0() == sphere + assert test.get_contact(0).get_node1() == heightfield + + # Increment sphere's Z coordinate, no longer colliding + np1.set_pos(0, 0, 2) + test = world.contact_test_pair(sphere, heightfield) + assert test.get_num_contacts() == 0 From df9640454fe57023506450bf2bdf4c1921de1f8b Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 15 Apr 2019 15:57:57 -0600 Subject: [PATCH 10/77] cocoadisplay: Add missing EXPCL_PANDA_COCOADISPLAY --- panda/src/cocoadisplay/cocoaGraphicsBuffer.h | 2 +- panda/src/cocoadisplay/cocoaGraphicsPipe.h | 2 +- panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h | 2 +- panda/src/cocoadisplay/cocoaGraphicsWindow.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsBuffer.h b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h index 3167d2c02f..8485540fdc 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsBuffer.h +++ b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h @@ -21,7 +21,7 @@ * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface * with Cocoa contexts, so that it can be used without a host window. */ -class CocoaGraphicsBuffer : public GLGraphicsBuffer { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsBuffer : public GLGraphicsBuffer { public: CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe, const std::string &name, diff --git a/panda/src/cocoadisplay/cocoaGraphicsPipe.h b/panda/src/cocoadisplay/cocoaGraphicsPipe.h index cc8c8142d5..902d673047 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsPipe.h +++ b/panda/src/cocoadisplay/cocoaGraphicsPipe.h @@ -33,7 +33,7 @@ class FrameBufferProperties; * This graphics pipe represents the interface for creating OpenGL graphics * windows on a Cocoa-based (e.g. Mac OS X) client. */ -class CocoaGraphicsPipe : public GraphicsPipe { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsPipe : public GraphicsPipe { public: CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID()); virtual ~CocoaGraphicsPipe(); diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index c8820a73d3..0463372b8e 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -26,7 +26,7 @@ * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific * information. */ -class CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian { public: INLINE const FrameBufferProperties &get_fb_properties() const; void get_properties(FrameBufferProperties &properties, diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index a053cb11b9..99e8814f63 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -29,7 +29,7 @@ * An interface to the Cocoa system for managing OpenGL windows under Mac OS * X. */ -class CocoaGraphicsWindow : public GraphicsWindow { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsWindow : public GraphicsWindow { public: CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, const std::string &name, From be247e1be9236cadb9a7359e77cab86993d203e6 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 15 Apr 2019 16:46:06 -0600 Subject: [PATCH 11/77] makepanda: Remove config entry for defunct HAVE_SOFTIMAGE --- makepanda/makepanda.py | 1 - 1 file changed, 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 25ee8a3a80..6a10cae138 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2364,7 +2364,6 @@ DTOOL_CONFIG=[ ("COMPILE_IN_DEFAULT_FONT", '1', '1'), ("STDFLOAT_DOUBLE", 'UNDEF', 'UNDEF'), ("HAVE_MAYA", '1', '1'), - ("HAVE_SOFTIMAGE", 'UNDEF', 'UNDEF'), ("REPORT_OPENSSL_ERRORS", '1', '1'), ("USE_PANDAFILESTREAM", '1', '1'), ("USE_DELETED_CHAIN", '1', '1'), From 186d8feef4f168b1b30b7b1bd07de397a01c0cc0 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Apr 2019 21:49:23 +0200 Subject: [PATCH 12/77] movies: properly detect extension of pz/gz audio/video files --- panda/src/movies/movieTypeRegistry.cxx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/panda/src/movies/movieTypeRegistry.cxx b/panda/src/movies/movieTypeRegistry.cxx index 5bc8228bb4..dca0ef9400 100644 --- a/panda/src/movies/movieTypeRegistry.cxx +++ b/panda/src/movies/movieTypeRegistry.cxx @@ -31,6 +31,12 @@ PT(MovieAudio) MovieTypeRegistry:: make_audio(const Filename &name) { string ext = downcase(name.get_extension()); +#ifdef HAVE_ZLIB + if (ext == "pz" || ext == "gz") { + ext = Filename(name.get_basename_wo_extension()).get_extension(); + } +#endif + _audio_lock.lock(); // Make sure that the list of audio types has been read in. @@ -154,6 +160,12 @@ PT(MovieVideo) MovieTypeRegistry:: make_video(const Filename &name) { string ext = downcase(name.get_extension()); +#ifdef HAVE_ZLIB + if (ext == "pz" || ext == "gz") { + ext = Filename(name.get_basename_wo_extension()).get_extension(); + } +#endif + _video_lock.lock(); // Make sure that the list of video types has been read in. From ca5b4e7b54d3f7bdf373400a985bbf33484264c4 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Apr 2019 22:28:55 +0200 Subject: [PATCH 13/77] ode: fix OdeJoint.attach with None parameters Fixes #633 --- panda/src/ode/odeJoint.h | 2 +- panda/src/ode/odeJoint_ext.cxx | 19 ++++++++++++++- panda/src/ode/odeJoint_ext.h | 2 +- tests/ode/conftest.py | 7 ++++++ tests/ode/test_ode_joints.py | 43 ++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/ode/conftest.py create mode 100644 tests/ode/test_ode_joints.py diff --git a/panda/src/ode/odeJoint.h b/panda/src/ode/odeJoint.h index eda2a98b96..47233a9361 100644 --- a/panda/src/ode/odeJoint.h +++ b/panda/src/ode/odeJoint.h @@ -83,7 +83,7 @@ PUBLISHED: INLINE void set_feedback(bool flag = true); INLINE OdeJointFeedback *get_feedback(); - EXTENSION(void attach(const OdeBody *body1, const OdeBody *body2)); + EXTENSION(void attach(PyObject *body1, PyObject *body2)); void attach_bodies(const OdeBody &body1, const OdeBody &body2); void attach_body(const OdeBody &body, int index); void detach(); diff --git a/panda/src/ode/odeJoint_ext.cxx b/panda/src/ode/odeJoint_ext.cxx index 6f284e1213..57669b7644 100644 --- a/panda/src/ode/odeJoint_ext.cxx +++ b/panda/src/ode/odeJoint_ext.cxx @@ -29,6 +29,7 @@ #include "odePlane2dJoint.h" #ifndef CPPPARSER +extern Dtool_PyTypedObject Dtool_OdeBody; extern Dtool_PyTypedObject Dtool_OdeJoint; extern Dtool_PyTypedObject Dtool_OdeBallJoint; extern Dtool_PyTypedObject Dtool_OdeHingeJoint; @@ -48,7 +49,23 @@ extern Dtool_PyTypedObject Dtool_OdePlane2dJoint; * attached to the environment. */ void Extension:: -attach(const OdeBody *body1, const OdeBody *body2) { +attach(PyObject *param1, PyObject *param2) { + const OdeBody *body1 = nullptr; + if (param1 != Py_None) { + body1 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param1, &Dtool_OdeBody, 1, "OdeJoint.attach", true, true); + if (body1 == nullptr) { + return; + } + } + + const OdeBody *body2 = nullptr; + if (param2 != Py_None) { + body2 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param2, &Dtool_OdeBody, 2, "OdeJoint.attach", true, true); + if (body2 == nullptr) { + return; + } + } + if (body1 && body2) { _this->attach_bodies(*body1, *body2); diff --git a/panda/src/ode/odeJoint_ext.h b/panda/src/ode/odeJoint_ext.h index b61938506f..84dbf4ac93 100644 --- a/panda/src/ode/odeJoint_ext.h +++ b/panda/src/ode/odeJoint_ext.h @@ -30,7 +30,7 @@ template<> class Extension : public ExtensionBase { public: - void attach(const OdeBody *body1, const OdeBody *body2); + void attach(PyObject *body1, PyObject *body2); PyObject *convert() const; }; diff --git a/tests/ode/conftest.py b/tests/ode/conftest.py new file mode 100644 index 0000000000..289304cf0f --- /dev/null +++ b/tests/ode/conftest.py @@ -0,0 +1,7 @@ +import pytest + + +@pytest.fixture +def world(): + ode = pytest.importorskip("panda3d.ode") + return ode.OdeWorld() diff --git a/tests/ode/test_ode_joints.py b/tests/ode/test_ode_joints.py new file mode 100644 index 0000000000..d1e202412d --- /dev/null +++ b/tests/ode/test_ode_joints.py @@ -0,0 +1,43 @@ +import pytest + + +def test_odejoint_attach_both(world): + from panda3d import ode + + body1 = ode.OdeBody(world) + body2 = ode.OdeBody(world) + + assert len(body1.joints) == 0 + assert len(body2.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(body1, body2) + + assert tuple(body1.joints) == (joint,) + assert tuple(body2.joints) == (joint,) + + +def test_odejoint_attach_0(world): + from panda3d import ode + + body = ode.OdeBody(world) + + assert len(body.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(body, None) + + assert tuple(body.joints) == (joint,) + + +def test_odejoint_attach_1(world): + from panda3d import ode + + body = ode.OdeBody(world) + + assert len(body.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(None, body) + + assert tuple(body.joints) == (joint,) From 00b3fbdb1ab9b9dce6ca8a88287563a7be687c9d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Apr 2019 22:57:06 +0200 Subject: [PATCH 14/77] movies: fix for loading unseekable ogg vorbis files Now, compressed/encrypted ogg files will properly be detected as unseekable, and will still be able to be played. --- panda/src/movies/vorbisAudioCursor.cxx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/panda/src/movies/vorbisAudioCursor.cxx b/panda/src/movies/vorbisAudioCursor.cxx index d3210ae46e..97666d0056 100644 --- a/panda/src/movies/vorbisAudioCursor.cxx +++ b/panda/src/movies/vorbisAudioCursor.cxx @@ -199,6 +199,22 @@ cb_seek_func(void *datasource, ogg_int64_t offset, int whence) { break; case SEEK_CUR: + // Vorbis uses a seek with offset 0 to determine whether seeking is + // supported, but this is not good enough. We seek to the end and back. + if (offset == 0) { + std::streambuf *buf = stream->rdbuf(); + std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (pos < 0) { + return -1; + } + if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) { + // It worked; seek back to the previous location. + buf->pubseekpos(pos, std::ios::in); + return 0; + } else { + return -1; + } + } stream->seekg(offset, std::ios::cur); break; From bfb50ab7ff85b409b93908e14897a3abd2a4a032 Mon Sep 17 00:00:00 2001 From: Rishabh Tewari Date: Sun, 28 Apr 2019 13:03:17 -0500 Subject: [PATCH 15/77] device: don't crash on Linux if no devices are found Fixes #634 Closes #635 --- panda/src/device/linuxInputDeviceManager.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 8eed3e1cc3..7b9b9fdf6f 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -61,6 +61,9 @@ LinuxInputDeviceManager() { // We'll want to sort the devices by index, since the order may be // meaningful (eg. for the Xbox wireless receiver). + if (indices.empty()) { + return; + } std::sort(indices.begin(), indices.end()); _evdev_devices.resize(indices.back() + 1, nullptr); From 00b5faca2d5905c514fadff08ef745636592a0b0 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 01:06:44 +0200 Subject: [PATCH 16/77] ode: add OdeBody.joints property --- panda/src/ode/odeBody.h | 1 + 1 file changed, 1 insertion(+) diff --git a/panda/src/ode/odeBody.h b/panda/src/ode/odeBody.h index f1cd9dae68..5cdfd4c8d1 100644 --- a/panda/src/ode/odeBody.h +++ b/panda/src/ode/odeBody.h @@ -133,6 +133,7 @@ PUBLISHED: OdeJoint get_joint(int index) const; MAKE_SEQ(get_joints, get_num_joints, get_joint); EXTENSION(INLINE PyObject *get_converted_joint(int i) const); + MAKE_SEQ_PROPERTY(joints, get_num_joints, get_converted_joint); INLINE void enable(); INLINE void disable(); From a7265922448436ce9c02aff3d04eaef788a76465 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 10:48:04 +0200 Subject: [PATCH 17/77] movies: support looping compressed .wav files Any kind of skipping is supported on any kind of stream, actually, but some uses may require reopening the file and skipping some number of bytes, so a warning will be displayed in some cases. I figure this feature is particularly interesting for .wav files since they are (1) often used for short samples, where skipping bytes is not really a big deal, and (2) aren't inherently compressed so would benefit particularly from zlib compression. --- panda/src/express/zStreamBuf.cxx | 4 +-- panda/src/movies/wavAudioCursor.cxx | 48 ++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/panda/src/express/zStreamBuf.cxx b/panda/src/express/zStreamBuf.cxx index 877254918d..57a64bed7a 100644 --- a/panda/src/express/zStreamBuf.cxx +++ b/panda/src/express/zStreamBuf.cxx @@ -202,8 +202,8 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) { gbump(n); - _source->seekg(0, ios::beg); - if (_source->tellg() == (streampos)0) { + if (_source->rdbuf()->pubseekpos(0, ios::in) == (streampos)0) { + _source->clear(); _z_source.next_in = Z_NULL; _z_source.avail_in = 0; _z_source.next_out = Z_NULL; diff --git a/panda/src/movies/wavAudioCursor.cxx b/panda/src/movies/wavAudioCursor.cxx index 381ce2d81b..4b58794d46 100644 --- a/panda/src/movies/wavAudioCursor.cxx +++ b/panda/src/movies/wavAudioCursor.cxx @@ -294,27 +294,61 @@ seek(double t) { t = std::max(t, 0.0); std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size); + std::streambuf *buf = _stream->rdbuf(); + if (_can_seek_fast) { - _stream->seekg(pos); - if (_stream->tellg() != pos) { + if (buf->pubseekpos(pos, std::ios::in) != pos) { // Clearly, we can't seek fast. Fall back to the case below. _can_seek_fast = false; } } - if (!_can_seek_fast) { - std::streampos current = _stream->tellg(); + // Get the current position of the cursor in the file. + std::streampos current = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (!_can_seek_fast) { if (pos > current) { // It is ahead of our current position. Skip ahead. - _reader.skip_bytes(pos - current); + _stream->ignore(pos - current); + current = pos; } else if (pos < current) { - // We'll have to reopen the file. TODO + // Can we seek to the beginning? Some streams, such as ZStream, let us + // rewind the stream. + if (buf->pubseekpos(0, std::ios::in) == 0) { + if (pos > _data_start && movies_cat.is_info()) { + Filename fn = get_source()->get_filename(); + movies_cat.info() + << "Unable to seek backwards in " << fn.get_basename() + << "; seeking to beginning and skipping " << pos << " bytes.\n"; + } + _stream->ignore(pos); + current = pos; + } else { + // No; close and reopen the file. + Filename fn = get_source()->get_filename(); + movies_cat.warning() + << "Unable to seek backwards in " << fn.get_basename() + << "; reopening and skipping " << pos << " bytes.\n"; + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *stream = vfs->open_read_file(get_source()->get_filename(), true); + if (stream != nullptr) { + vfs->close_read_file(_stream); + stream->ignore(pos); + _stream = stream; + _reader = StreamReader(stream, false); + current = pos; + } else { + movies_cat.error() + << "Unable to reopen " << fn << ".\n"; + _can_seek = false; + } + } } } - _data_pos = _stream->tellg() - _data_start; + _data_pos = (size_t)current - _data_start; _last_seek = _data_pos / _byte_rate; _samples_read = 0; } From 2c55663472f50f5e50ea0a605a9f15bf90822fb0 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 11:39:30 +0200 Subject: [PATCH 18/77] movies: allow seeking compressed Ogg Vorbis file to beginning --- panda/src/movies/vorbisAudioCursor.cxx | 43 +++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/panda/src/movies/vorbisAudioCursor.cxx b/panda/src/movies/vorbisAudioCursor.cxx index 97666d0056..80c5613b0c 100644 --- a/panda/src/movies/vorbisAudioCursor.cxx +++ b/panda/src/movies/vorbisAudioCursor.cxx @@ -91,19 +91,46 @@ seek(double t) { t = std::max(t, 0.0); // Use ov_time_seek_lap if cross-lapping is enabled. + int result; if (vorbis_seek_lap) { - if (ov_time_seek_lap(&_ov, t) != 0) { - movies_cat.error() - << "Seek failed. Ogg Vorbis stream may not be seekable.\n"; - return; - } + result = ov_time_seek_lap(&_ov, t); } else { - if (ov_time_seek(&_ov, t) != 0) { - movies_cat.error() - << "Seek failed. Ogg Vorbis stream may not be seekable.\n"; + result = ov_time_seek(&_ov, t); + } + + // Special case for seeking to the beginning; if normal seek fails, we may + // be able to explicitly seek to the beginning of the file and call ov_open + // again. This allows looping compressed .ogg files. + if (result == OV_ENOSEEK && t == 0.0) { + std::istream *stream = (std::istream *)_ov.datasource; + + if (stream->rdbuf()->pubseekpos(0, std::ios::in) == 0) { + // Back up the callbacks, then destroy the stream, making sure to first + // unset the datasource so that it won't close the file. + ov_callbacks callbacks = _ov.callbacks; + _ov.datasource = nullptr; + ov_clear(&_ov); + + if (ov_open_callbacks((void *)stream, &_ov, nullptr, 0, callbacks) != 0) { + movies_cat.error() + << "Failed to reopen Ogg Vorbis file to seek to beginning.\n"; + return; + } + + // Reset these fields for good measure, just in case the file changed. + vorbis_info *vi = ov_info(&_ov, -1); + _audio_channels = vi->channels; + _audio_rate = vi->rate; + + _last_seek = 0.0; + _samples_read = 0; return; } } + if (result != 0) { + movies_cat.error() + << "Seek failed. Ogg Vorbis stream may not be seekable.\n"; + } _last_seek = ov_time_tell(&_ov); _samples_read = 0; From fa1ff3d48914ba115565e04babb8fda82ad7f32b Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 11:41:13 +0200 Subject: [PATCH 19/77] movies: fix for loading unseekable Opus files Same fix as 00b3fbdb1ab9b9dce6ca8a88287563a7be687c9d but for Opus files. --- panda/src/movies/opusAudioCursor.cxx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index 8554215088..b5fb8b31b5 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -56,6 +56,22 @@ int cb_seek(void *stream, opus_int64 offset, int whence) { break; case SEEK_CUR: + // opusfile uses a seek with offset 0 to determine whether seeking is + // supported, but this is not good enough. We seek to the end and back. + if (offset == 0) { + std::streambuf *buf = in->rdbuf(); + std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (pos < 0) { + return -1; + } + if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) { + // It worked; seek back to the previous location. + buf->pubseekpos(pos, std::ios::in); + return 0; + } else { + return -1; + } + } in->seekg(offset, std::ios::cur); break; From 8af3e37bd2f8f963138fc07189b216bc4876ff90 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 12:14:04 +0200 Subject: [PATCH 20/77] movies: remove unused fields from Vorbis and Opus audio cursors --- panda/src/movies/opusAudioCursor.h | 8 -------- panda/src/movies/vorbisAudioCursor.h | 9 --------- 2 files changed, 17 deletions(-) diff --git a/panda/src/movies/opusAudioCursor.h b/panda/src/movies/opusAudioCursor.h index b3996c73bb..825e914856 100644 --- a/panda/src/movies/opusAudioCursor.h +++ b/panda/src/movies/opusAudioCursor.h @@ -47,14 +47,6 @@ protected: OggOpusFile *_op; int _link; - double _byte_rate; - int _block_align; - int _bytes_per_sample; - bool _is_float; - - std::streampos _data_start; - std::streampos _data_pos; - size_t _data_size; public: static TypeHandle get_class_type() { diff --git a/panda/src/movies/vorbisAudioCursor.h b/panda/src/movies/vorbisAudioCursor.h index d194cb0c23..c82eec8184 100644 --- a/panda/src/movies/vorbisAudioCursor.h +++ b/panda/src/movies/vorbisAudioCursor.h @@ -50,16 +50,7 @@ protected: #ifndef CPPPARSER OggVorbis_File _ov; #endif - int _bitstream; - double _byte_rate; - int _block_align; - int _bytes_per_sample; - bool _is_float; - - std::streampos _data_start; - std::streampos _data_pos; - size_t _data_size; public: static TypeHandle get_class_type() { From d92168cd74241f3edc94e68d5b7449de40314c10 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 12:39:44 +0200 Subject: [PATCH 21/77] movies: allow seeking compressed Opus file to beginning Same change as 2c55663472f50f5e50ea0a605a9f15bf90822fb0 but for Opus files. --- panda/src/movies/opusAudioCursor.cxx | 44 +++++++++++++++++++++------- panda/src/movies/opusAudioCursor.h | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index b5fb8b31b5..788a882885 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -117,16 +117,7 @@ opus_int64 cb_tell(void *stream) { return in->tellg(); } -int cb_close(void *stream) { - istream *in = (istream *)stream; - nassertr(in != nullptr, EOF); - - VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); - vfs->close_read_file(in); - return 0; -} - -static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close}; +static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, nullptr}; TypeHandle OpusAudioCursor::_type_handle; @@ -138,6 +129,7 @@ OpusAudioCursor:: OpusAudioCursor(OpusAudio *src, istream *stream) : MovieAudioCursor(src), _is_valid(false), + _stream(stream), _link(0) { nassertv(stream != nullptr); @@ -175,6 +167,11 @@ OpusAudioCursor:: op_free(_op); _op = nullptr; } + + if (_stream != nullptr) { + VirtualFileSystem::close_read_file(_stream); + _stream = nullptr; + } } /** @@ -190,7 +187,32 @@ seek(double t) { t = std::max(t, 0.0); // Use op_time_seek_lap if cross-lapping is enabled. - int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0)); + ogg_int64_t sample = (ogg_int64_t)(t * 48000.0); + int error = op_pcm_seek(_op, sample); + + // Special case for seeking to the beginning; if normal seek fails, we may + // be able to explicitly seek to the beginning of the file and call op_open + // again. This allows looping compressed .opus files. + if (error == OP_ENOSEEK && sample == 0) { + if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == 0) { + OggOpusFile *op = op_open_callbacks((void *)_stream, &callbacks, nullptr, 0, nullptr); + if (op != nullptr) { + op_free(_op); + _op = op; + } else { + movies_cat.error() + << "Failed to reopen Opus file to seek to beginning.\n"; + return; + } + + // Reset this field for good measure, just in case this changed. + _audio_channels = op_channel_count(_op, -1); + + _last_seek = 0.0; + _samples_read = 0; + return; + } + } if (error != 0) { movies_cat.error() << "Seek failed (error " << error << "). Opus stream may not be seekable.\n"; diff --git a/panda/src/movies/opusAudioCursor.h b/panda/src/movies/opusAudioCursor.h index 825e914856..11e05e4030 100644 --- a/panda/src/movies/opusAudioCursor.h +++ b/panda/src/movies/opusAudioCursor.h @@ -45,7 +45,7 @@ public: protected: OggOpusFile *_op; - + std::istream *_stream; int _link; public: From 026c2bf619b07a3643ec18e1125efdf8e35987a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 17:49:02 +0200 Subject: [PATCH 22/77] movies: UserDataAudio.read_samples should take a bytes/vector_uchar --- panda/src/movies/userDataAudio.cxx | 4 ++-- panda/src/movies/userDataAudio.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panda/src/movies/userDataAudio.cxx b/panda/src/movies/userDataAudio.cxx index f02726d10b..4022b6b7f3 100644 --- a/panda/src/movies/userDataAudio.cxx +++ b/panda/src/movies/userDataAudio.cxx @@ -107,11 +107,11 @@ append(DatagramIterator *src, int n) { * but it may be convenient to deal with samples in python. */ void UserDataAudio:: -append(const std::string &str) { +append(const vector_uchar &str) { nassertv(!_aborted); int samples = str.size() / (2 * _desired_channels); int words = samples * _desired_channels; - for (int i=0; i Date: Mon, 29 Apr 2019 17:55:15 +0200 Subject: [PATCH 23/77] movies: return actual number of samples read in read_samples Various codecs may only partially fill the buffer and fill the rest with zeroes, but it is useful to know how many samples were actually read. --- panda/src/ffmpeg/ffmpegAudioCursor.cxx | 3 ++- panda/src/ffmpeg/ffmpegAudioCursor.h | 2 +- panda/src/movies/flacAudioCursor.cxx | 6 ++++-- panda/src/movies/flacAudioCursor.h | 2 +- panda/src/movies/microphoneAudioDS.cxx | 5 +++-- panda/src/movies/movieAudioCursor.cxx | 5 +++-- panda/src/movies/movieAudioCursor.h | 2 +- panda/src/movies/opusAudioCursor.cxx | 4 +++- panda/src/movies/opusAudioCursor.h | 2 +- panda/src/movies/userDataAudio.cxx | 9 ++++++--- panda/src/movies/userDataAudio.h | 2 +- panda/src/movies/userDataAudioCursor.cxx | 9 ++++++--- panda/src/movies/userDataAudioCursor.h | 2 +- panda/src/movies/vorbisAudioCursor.cxx | 3 ++- panda/src/movies/vorbisAudioCursor.h | 2 +- panda/src/movies/wavAudioCursor.cxx | 8 +++++--- panda/src/movies/wavAudioCursor.h | 2 +- 17 files changed, 42 insertions(+), 26 deletions(-) diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.cxx b/panda/src/ffmpeg/ffmpegAudioCursor.cxx index 4b17800c2b..2fdc10b983 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.cxx +++ b/panda/src/ffmpeg/ffmpegAudioCursor.cxx @@ -462,7 +462,7 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void FfmpegAudioCursor:: +int FfmpegAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; @@ -486,4 +486,5 @@ read_samples(int n, int16_t *data) { } _samples_read += n; + return n; } diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.h b/panda/src/ffmpeg/ffmpegAudioCursor.h index f3963ff527..77e8890d6c 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.h +++ b/panda/src/ffmpeg/ffmpegAudioCursor.h @@ -45,7 +45,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); protected: void fetch_packet(); diff --git a/panda/src/movies/flacAudioCursor.cxx b/panda/src/movies/flacAudioCursor.cxx index 0e3bbb163a..1c9d06984b 100644 --- a/panda/src/movies/flacAudioCursor.cxx +++ b/panda/src/movies/flacAudioCursor.cxx @@ -118,8 +118,10 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void FlacAudioCursor:: +int FlacAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; - _samples_read += drflac_read_s16(_drflac, desired, data) / _audio_channels; + n = drflac_read_s16(_drflac, desired, data) / _audio_channels; + _samples_read += n; + return n; } diff --git a/panda/src/movies/flacAudioCursor.h b/panda/src/movies/flacAudioCursor.h index 995be42e1e..93a48e5f32 100644 --- a/panda/src/movies/flacAudioCursor.h +++ b/panda/src/movies/flacAudioCursor.h @@ -37,7 +37,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; diff --git a/panda/src/movies/microphoneAudioDS.cxx b/panda/src/movies/microphoneAudioDS.cxx index 1ce78b0209..c04da23c3e 100644 --- a/panda/src/movies/microphoneAudioDS.cxx +++ b/panda/src/movies/microphoneAudioDS.cxx @@ -91,7 +91,7 @@ public: int _samples_per_buffer; public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); virtual int ready() const; public: @@ -323,7 +323,7 @@ MicrophoneAudioCursorDS:: /** * */ -void MicrophoneAudioCursorDS:: +int MicrophoneAudioCursorDS:: read_samples(int n, int16_t *data) { int orign = n; if (_handle) { @@ -373,6 +373,7 @@ read_samples(int n, int16_t *data) { if (n > 0) { memcpy(data, 0, n*2*_audio_channels); } + return orign - n; } /** diff --git a/panda/src/movies/movieAudioCursor.cxx b/panda/src/movies/movieAudioCursor.cxx index 33acc0e10c..a725b56ad1 100644 --- a/panda/src/movies/movieAudioCursor.cxx +++ b/panda/src/movies/movieAudioCursor.cxx @@ -45,14 +45,14 @@ MovieAudioCursor:: * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void MovieAudioCursor:: +int MovieAudioCursor:: read_samples(int n, int16_t *data) { // This is the null implementation, which generates pure silence. Normally, // this method will be overridden by a subclass. if (n <= 0) { - return; + return 0; } int desired = n * _audio_channels; @@ -60,6 +60,7 @@ read_samples(int n, int16_t *data) { data[i] = 0; } _samples_read += n; + return n; } /** diff --git a/panda/src/movies/movieAudioCursor.h b/panda/src/movies/movieAudioCursor.h index b2d5d9bebe..28674f1215 100644 --- a/panda/src/movies/movieAudioCursor.h +++ b/panda/src/movies/movieAudioCursor.h @@ -51,7 +51,7 @@ PUBLISHED: std::string read_samples(int n); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); protected: PT(MovieAudio) _source; diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index 788a882885..814dc8fe04 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -228,7 +228,7 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void OpusAudioCursor:: +int OpusAudioCursor:: read_samples(int n, int16_t *data) { int16_t *end = data + (n * _audio_channels); @@ -262,7 +262,9 @@ read_samples(int n, int16_t *data) { // Fill the rest of the buffer with silence. if (data < end) { memset(data, 0, (unsigned char *)end - (unsigned char *)data); + n -= (end - data) / _audio_channels; } + return n; } #endif // HAVE_OPUS diff --git a/panda/src/movies/opusAudioCursor.h b/panda/src/movies/opusAudioCursor.h index 11e05e4030..3e2137de5e 100644 --- a/panda/src/movies/opusAudioCursor.h +++ b/panda/src/movies/opusAudioCursor.h @@ -39,7 +39,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; diff --git a/panda/src/movies/userDataAudio.cxx b/panda/src/movies/userDataAudio.cxx index 4022b6b7f3..9490432dc0 100644 --- a/panda/src/movies/userDataAudio.cxx +++ b/panda/src/movies/userDataAudio.cxx @@ -57,12 +57,14 @@ open() { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void UserDataAudio:: +int UserDataAudio:: read_samples(int n, int16_t *data) { int ready = (_data.size() / _desired_channels); int desired = n * _desired_channels; - int avail = ready * _desired_channels; - if (avail > desired) avail = desired; + if (n > ready) { + n = ready; + } + int avail = n * _desired_channels; for (int i=0; i_remove_after_read) { - source->read_samples(n, data); + if (source->_remove_after_read) { + n = source->read_samples(n, data); } else { int offset = _samples_read * _audio_channels; @@ -66,9 +66,12 @@ read_samples(int n, int16_t *data) { for (int i=avail; itellg() - _data_start; - _samples_read += read_samples / _audio_channels; + _samples_read += n; + return n; } diff --git a/panda/src/movies/wavAudioCursor.h b/panda/src/movies/wavAudioCursor.h index 21133b1c62..9ae03a5d7d 100644 --- a/panda/src/movies/wavAudioCursor.h +++ b/panda/src/movies/wavAudioCursor.h @@ -31,7 +31,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; From c329a41dd3d25f6646136fe3be11edf37dc451bc Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:00:29 +0200 Subject: [PATCH 24/77] movies: Python-facing read_samples should return bytes object Also, properly returns partial buffers based on number of actual samples read --- panda/src/movies/movieAudioCursor.cxx | 27 ++++++++++++++++----------- panda/src/movies/movieAudioCursor.h | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/panda/src/movies/movieAudioCursor.cxx b/panda/src/movies/movieAudioCursor.cxx index a725b56ad1..a836937455 100644 --- a/panda/src/movies/movieAudioCursor.cxx +++ b/panda/src/movies/movieAudioCursor.cxx @@ -93,23 +93,28 @@ read_samples(int n, Datagram *dg) { * This is not particularly efficient, but it may be a convenient way to * manipulate samples in python. */ -std::string MovieAudioCursor:: +vector_uchar MovieAudioCursor:: read_samples(int n) { - std::ostringstream result; + vector_uchar result; int16_t tmp[4096]; while (n > 0) { int blocksize = (4096 / _audio_channels); - if (blocksize > n) blocksize = n; - int words = blocksize * _audio_channels; - read_samples(blocksize, tmp); - for (int i=0; i>8) & 255)); + if (blocksize > n) { + blocksize = n; } - n -= blocksize; + int nread = read_samples(blocksize, tmp); + if (nread == 0) { + return result; + } + int words = nread * _audio_channels; + for (int i = 0; i < words; ++i) { + int16_t word = tmp[i]; + result.push_back((uint8_t)(word & 255u)); + result.push_back((uint8_t)((word >> 8) & 255u)); + } + n -= nread; } - return result.str(); + return result; } diff --git a/panda/src/movies/movieAudioCursor.h b/panda/src/movies/movieAudioCursor.h index 28674f1215..e7c9668713 100644 --- a/panda/src/movies/movieAudioCursor.h +++ b/panda/src/movies/movieAudioCursor.h @@ -48,7 +48,7 @@ PUBLISHED: virtual int ready() const; virtual void seek(double offset); void read_samples(int n, Datagram *dg); - std::string read_samples(int n); + vector_uchar read_samples(int n); public: virtual int read_samples(int n, int16_t *data); From 2f2354550dd322d3263f66da6763fd88e061f0ec Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:02:56 +0200 Subject: [PATCH 25/77] movies: set length of unseekable ogg/Opus streams upon reaching EOF This is one of a series of commits that will make it possible to loop compressed ogg and Opus streams. --- panda/src/movies/opusAudioCursor.cxx | 3 +++ panda/src/movies/vorbisAudioCursor.cxx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index 814dc8fe04..3aee7c5844 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -240,6 +240,9 @@ read_samples(int n, int16_t *data) { data += read_samples * _audio_channels; _samples_read += read_samples; } else { + if (read_samples == 0 && _length == 1.0E10) { + _length = op_pcm_tell(_op) / 48000.0; + } break; } diff --git a/panda/src/movies/vorbisAudioCursor.cxx b/panda/src/movies/vorbisAudioCursor.cxx index f049d925d0..09fbf5ad14 100644 --- a/panda/src/movies/vorbisAudioCursor.cxx +++ b/panda/src/movies/vorbisAudioCursor.cxx @@ -158,6 +158,9 @@ read_samples(int n, int16_t *data) { buffer += read_bytes; length -= read_bytes; } else { + if (read_bytes == 0 && _length == 1.0E10) { + _length = ov_time_tell(&_ov); + } break; } From 44f4ad94baf027231dac7b0a0d0eea25ca6de772 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:04:38 +0200 Subject: [PATCH 26/77] audio: allow looping of streams with unknown length --- panda/src/audiotraits/openalAudioManager.cxx | 2 +- panda/src/audiotraits/openalAudioSound.cxx | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index edf7dfe222..76e7b0dd81 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -442,7 +442,7 @@ get_sound_data(MovieAudio *movie, int mode) { int channels = stream->audio_channels(); int samples = (int)(stream->length() * stream->audio_rate()); int16_t *data = new int16_t[samples * channels]; - stream->read_samples(samples, data); + samples = stream->read_samples(samples, data); alBufferData(sd->_sample, (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data, samples * channels * 2, stream->audio_rate()); diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 9244b7509f..5676e63f80 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -373,7 +373,6 @@ read_stream_data(int bytelen, unsigned char *buffer) { nassertr(has_sound_data(), 0); MovieAudioCursor *cursor = _sd->_stream; - double length = cursor->length(); int channels = cursor->audio_channels(); int rate = cursor->audio_rate(); int space = bytelen / (channels * 2); @@ -381,7 +380,7 @@ read_stream_data(int bytelen, unsigned char *buffer) { while (space && (_loops_completed < _playing_loops)) { double t = cursor->tell(); - double remain = length - t; + double remain = cursor->length() - t; if (remain > 60.0) { remain = 60.0; } @@ -403,9 +402,20 @@ read_stream_data(int bytelen, unsigned char *buffer) { if (samples > _sd->_stream->ready()) { samples = _sd->_stream->ready(); } - cursor->read_samples(samples, (int16_t *)buffer); - size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2); - audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval); + samples = cursor->read_samples(samples, (int16_t *)buffer); + if (audio_cat.is_debug()) { + size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2); + audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval); + } + if (samples == 0) { + _loops_completed += 1; + cursor->seek(0.0); + if (_playing_loops >= 1000000000) { + // Prevent infinite loop if endlessly looping empty sound + return fill; + } + continue; + } fill += samples; space -= samples; buffer += (samples * channels * 2); From 3f4d85574a91a084ca92952404a533a1d6652ec3 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:05:29 +0200 Subject: [PATCH 27/77] tests: add UserDataAudio unit tests --- tests/movies/test_user_audio.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/movies/test_user_audio.py diff --git a/tests/movies/test_user_audio.py b/tests/movies/test_user_audio.py new file mode 100644 index 0000000000..13addd05f0 --- /dev/null +++ b/tests/movies/test_user_audio.py @@ -0,0 +1,15 @@ +import pytest + +from panda3d.core import UserDataAudio + + +@pytest.mark.parametrize("remove_after_read", [True, False]) +def test_userdata_audio(remove_after_read): + audio = UserDataAudio(48000, 2, remove_after_read) + audio.append(b'abcdefgh') + audio.done() + cursor = audio.open() + assert cursor.read_samples(0) == b'' + assert cursor.read_samples(1) == b'abcd' + assert cursor.read_samples(1) == b'efgh' + assert cursor.read_samples(1) == b'' From 269d3db290314e6e35d9f2c236e1c604f7421446 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:09:19 +0200 Subject: [PATCH 28/77] collide: fix typo in respect_prev_transform property --- panda/src/collide/collisionTraverser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index 8a6c912aaf..a51a1bd474 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -49,7 +49,7 @@ PUBLISHED: INLINE void set_respect_prev_transform(bool flag); INLINE bool get_respect_prev_transform() const; - MAKE_PROPERTY(respect_preV_transform, get_respect_prev_transform, + MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform, set_respect_prev_transform); void add_collider(const NodePath &collider, CollisionHandler *handler); From 5d6b4f4a77215daa3302c265f44d0076ba2bd61c Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 18:17:54 +0200 Subject: [PATCH 29/77] dtool: add StreamReader/StreamWriter move ctor and assignment ops --- dtool/src/prc/streamReader.I | 26 +++++++++++++++++++++++++- dtool/src/prc/streamReader.h | 2 ++ dtool/src/prc/streamWriter.I | 29 ++++++++++++++++++++++++++++- dtool/src/prc/streamWriter.h | 2 ++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/dtool/src/prc/streamReader.I b/dtool/src/prc/streamReader.I index e15953aeba..36f8b93e92 100644 --- a/dtool/src/prc/streamReader.I +++ b/dtool/src/prc/streamReader.I @@ -43,7 +43,18 @@ StreamReader(const StreamReader ©) : } /** - * The copy constructor does not copy ownership of the stream. + * The move constructor steals ownership of the stream. + */ +INLINE StreamReader:: +StreamReader(StreamReader &&from) noexcept : + _in(from._in), + _owns_stream(from._owns_stream) +{ + from._owns_stream = false; +} + +/** + * The copy assignment operator does not copy ownership of the stream. */ INLINE void StreamReader:: operator = (const StreamReader ©) { @@ -54,6 +65,19 @@ operator = (const StreamReader ©) { _owns_stream = false; } +/** + * The move assignment operator steals ownership of the stream. + */ +INLINE void StreamReader:: +operator = (StreamReader &&from) noexcept { + if (_owns_stream) { + delete _in; + } + _in = from._in; + _owns_stream = from._owns_stream; + from._owns_stream = false; +} + /** * */ diff --git a/dtool/src/prc/streamReader.h b/dtool/src/prc/streamReader.h index a317f0a783..4d32e0616e 100644 --- a/dtool/src/prc/streamReader.h +++ b/dtool/src/prc/streamReader.h @@ -31,7 +31,9 @@ public: PUBLISHED: INLINE explicit StreamReader(std::istream *in, bool owns_stream); INLINE StreamReader(const StreamReader ©); + INLINE StreamReader(StreamReader &&from) noexcept; INLINE void operator = (const StreamReader ©); + INLINE void operator = (StreamReader &&from) noexcept; INLINE ~StreamReader(); INLINE std::istream *get_istream() const; diff --git a/dtool/src/prc/streamWriter.I b/dtool/src/prc/streamWriter.I index b2485d3fc7..1f12c1517e 100644 --- a/dtool/src/prc/streamWriter.I +++ b/dtool/src/prc/streamWriter.I @@ -51,7 +51,21 @@ StreamWriter(const StreamWriter ©) : } /** - * The copy constructor does not copy ownership of the stream. + * The move constructor steals ownership of the stream. + */ +INLINE StreamWriter:: +StreamWriter(StreamWriter &&from) noexcept : +#ifdef HAVE_PYTHON + softspace(0), +#endif + _out(from._out), + _owns_stream(from._owns_stream) +{ + from._owns_stream = false; +} + +/** + * The copy assignment operator does not copy ownership of the stream. */ INLINE void StreamWriter:: operator = (const StreamWriter ©) { @@ -62,6 +76,19 @@ operator = (const StreamWriter ©) { _owns_stream = false; } +/** + * The move assignment operator steals ownership of the stream. + */ +INLINE void StreamWriter:: +operator = (StreamWriter &&from) noexcept { + if (_owns_stream) { + delete _out; + } + _out = from._out; + _owns_stream = from._owns_stream; + from._owns_stream = false; +} + /** * */ diff --git a/dtool/src/prc/streamWriter.h b/dtool/src/prc/streamWriter.h index cc00ef9ce3..8072e8aa40 100644 --- a/dtool/src/prc/streamWriter.h +++ b/dtool/src/prc/streamWriter.h @@ -32,7 +32,9 @@ public: PUBLISHED: INLINE explicit StreamWriter(std::ostream *out, bool owns_stream); INLINE StreamWriter(const StreamWriter ©); + INLINE StreamWriter(StreamWriter &&from) noexcept; INLINE void operator = (const StreamWriter ©); + INLINE void operator = (StreamWriter &&from) noexcept; INLINE ~StreamWriter(); INLINE std::ostream *get_ostream() const; From 333db2b17e898e42dcaf704676dd2056d4fd7117 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 22:28:59 +0200 Subject: [PATCH 30/77] movies: fix compilation issue in MSVC 2015 --- panda/src/movies/vorbisAudioCursor.cxx | 2 +- panda/src/movies/wavAudioCursor.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/movies/vorbisAudioCursor.cxx b/panda/src/movies/vorbisAudioCursor.cxx index 80c5613b0c..c84dd37aa7 100644 --- a/panda/src/movies/vorbisAudioCursor.cxx +++ b/panda/src/movies/vorbisAudioCursor.cxx @@ -104,7 +104,7 @@ seek(double t) { if (result == OV_ENOSEEK && t == 0.0) { std::istream *stream = (std::istream *)_ov.datasource; - if (stream->rdbuf()->pubseekpos(0, std::ios::in) == 0) { + if (stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { // Back up the callbacks, then destroy the stream, making sure to first // unset the datasource so that it won't close the file. ov_callbacks callbacks = _ov.callbacks; diff --git a/panda/src/movies/wavAudioCursor.cxx b/panda/src/movies/wavAudioCursor.cxx index 4b58794d46..92fa9dc4a9 100644 --- a/panda/src/movies/wavAudioCursor.cxx +++ b/panda/src/movies/wavAudioCursor.cxx @@ -315,7 +315,7 @@ seek(double t) { } else if (pos < current) { // Can we seek to the beginning? Some streams, such as ZStream, let us // rewind the stream. - if (buf->pubseekpos(0, std::ios::in) == 0) { + if (buf->pubseekpos(0, std::ios::in) == (std::streampos)0) { if (pos > _data_start && movies_cat.is_info()) { Filename fn = get_source()->get_filename(); movies_cat.info() From d9d30cdfd2b7d72e8933d7c0776660ee7803b8b8 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 29 Apr 2019 22:29:36 +0200 Subject: [PATCH 31/77] collide: compat for typo'ed respect_prev_transform property in 1.10 --- panda/src/collide/collisionTraverser.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index 8a6c912aaf..8ccd102bec 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -51,6 +51,8 @@ PUBLISHED: INLINE bool get_respect_prev_transform() const; MAKE_PROPERTY(respect_preV_transform, get_respect_prev_transform, set_respect_prev_transform); + MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform, + set_respect_prev_transform); void add_collider(const NodePath &collider, CollisionHandler *handler); bool remove_collider(const NodePath &collider); From 951218715626a10e260a936ab828fd44ea23e084 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 30 Apr 2019 10:32:01 +0200 Subject: [PATCH 32/77] test_wheel: upgrade pip inside virtualenv, don't write bytecode --- makepanda/test_wheel.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/makepanda/test_wheel.py b/makepanda/test_wheel.py index 09b0727fe7..1b6ac14175 100755 --- a/makepanda/test_wheel.py +++ b/makepanda/test_wheel.py @@ -17,31 +17,34 @@ from optparse import OptionParser def test_wheel(wheel, verbose=False): envdir = tempfile.mkdtemp(prefix="venv-") print("Setting up virtual environment in {0}".format(envdir)) - - if sys.version_info >= (3, 0): - subprocess.call([sys.executable, "-m", "venv", "--clear", envdir]) - else: - subprocess.call([sys.executable, "-m", "virtualenv", "--clear", envdir]) + sys.stdout.flush() # Make sure pip is up-to-date first. - if subprocess.call([sys.executable, "-m", "pip", "install", "-U", "pip"]) != 0: - shutil.rmtree(envdir) - sys.exit(1) + subprocess.call([sys.executable, "-B", "-m", "pip", "install", "-U", "pip"]) - # Install pytest into the environment, as well as our wheel. - if sys.platform == "win32": - pip = os.path.join(envdir, "Scripts", "pip.exe") + # Create a virtualenv. + if sys.version_info >= (3, 0): + subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir]) else: - pip = os.path.join(envdir, "bin", "pip") - if subprocess.call([pip, "install", "pytest", wheel]) != 0: - shutil.rmtree(envdir) - sys.exit(1) + subprocess.call([sys.executable, "-B", "-m", "virtualenv", "--clear", envdir]) - # Run the test suite. + # Determine the path to the Python interpreter. if sys.platform == "win32": python = os.path.join(envdir, "Scripts", "python.exe") else: python = os.path.join(envdir, "bin", "python") + + # Upgrade pip inside the environment too. + if subprocess.call([python, "-m", "pip", "install", "-U", "pip"]) != 0: + shutil.rmtree(envdir) + sys.exit(1) + + # Install pytest into the environment, as well as our wheel. + if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0: + shutil.rmtree(envdir) + sys.exit(1) + + # Run the test suite. test_cmd = [python, "-m", "pytest", "tests"] if verbose: test_cmd.append("--verbose") From 204cbe4464d694c299edb0d584bd7611557f234f Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 1 May 2019 17:05:12 +0200 Subject: [PATCH 33/77] interrogate: support unicode characters in Python 3 for 'char' arg Fixes #626 --- dtool/src/interrogate/interfaceMakerPythonNative.cxx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index dadd094ca4..c135ce3df4 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -4937,13 +4937,14 @@ write_function_instance(ostream &out, FunctionRemap *remap, expected_params += "NoneType"; } else if (TypeManager::is_char(type)) { - indent(out, indent_level) << "char " << param_name << default_expr << ";\n"; + indent(out, indent_level) << "char *" << param_name << "_str;\n"; + indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n"; - format_specifiers += "c"; - parameter_list += ", &" + param_name; + format_specifiers += "s#"; + parameter_list += ", &" + param_name + "_str, &" + param_name + "_len"; + extra_param_check << " && " << param_name << "_len == 1"; - // extra_param_check << " && isascii(" << param_name << ")"; - pexpr_string = "(char) " + param_name; + pexpr_string = param_name + "_str[0]"; expected_params += "char"; only_pyobjects = false; From d786709a49e5bd61ac020de9584901972ede8143 Mon Sep 17 00:00:00 2001 From: nate97 Date: Tue, 30 Apr 2019 21:46:16 -0500 Subject: [PATCH 34/77] sfxplayer: fixes bug when using the "node argument" in SoundInterval, also fixes Py3 TypeError exception Closes #640 --- direct/src/showbase/SfxPlayer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/direct/src/showbase/SfxPlayer.py b/direct/src/showbase/SfxPlayer.py index 4f139a89c9..c50c498236 100644 --- a/direct/src/showbase/SfxPlayer.py +++ b/direct/src/showbase/SfxPlayer.py @@ -53,6 +53,8 @@ class SfxPlayer: d = node.getDistance(listenerNode) else: d = node.getDistance(base.cam) + if not cutoff: + cutoff = self.cutoffDistance if d == None or d > cutoff: volume = 0 else: @@ -70,9 +72,6 @@ class SfxPlayer: self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node=None, listenerNode = None, cutoff = None): if sfx: - if not cutoff: - cutoff = self.cutoffDistance - self.setFinalVolume(sfx, node, volume, listenerNode, cutoff) # don't start over if it's already playing, unless From 16c3ca5c875706e66af8d85c2bfd3886afce3f9c Mon Sep 17 00:00:00 2001 From: Sebastian Hoffmann Date: Wed, 1 May 2019 00:27:37 +0200 Subject: [PATCH 35/77] device: Fix swapped axes on right stick for Jess Colour Rumble Pad Closes #639 --- panda/src/device/evdevInputDevice.cxx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4ebbc55498..fc89b5a5a4 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -62,6 +62,9 @@ enum QuirkBits { // 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, + + // Axes on the right stick are swapped, using x for y and vice versa. + QB_right_axes_swapped = 64, }; static const struct DeviceMapping { @@ -81,7 +84,7 @@ static const struct DeviceMapping { // Steam Controller (wireless) {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller}, // Jess Tech Colour Rumble Pad - {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, + {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_right_axes_swapped}, // SPEED Link SL-6535-SBK-01 {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0}, // 8bitdo N30 Pro Controller @@ -488,7 +491,11 @@ init_device() { break; case ABS_Z: if (quirks & QB_rstick_from_z) { - axis = InputDevice::Axis::right_x; + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_y; + } else { + axis = InputDevice::Axis::right_x; + } } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; @@ -514,7 +521,11 @@ init_device() { break; case ABS_RZ: if (quirks & QB_rstick_from_z) { - axis = InputDevice::Axis::right_y; + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_x; + } else { + axis = InputDevice::Axis::right_y; + } } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; From 4d33db20280a726de2747538ef3bbea9a27c947c Mon Sep 17 00:00:00 2001 From: Maverick Liberty Date: Sun, 28 Apr 2019 16:00:17 -0400 Subject: [PATCH 36/77] DirectOptionMenu: Fix popup menu reset of the popup marker's pos Also defines popupMarker_pos and raises an assertion error if #showPopupMenu() is called when no items have been specified. Fixes #636 Closes #637 --- direct/src/gui/DirectOptionMenu.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/direct/src/gui/DirectOptionMenu.py b/direct/src/gui/DirectOptionMenu.py index 40d48f7672..74d33b5e9e 100644 --- a/direct/src/gui/DirectOptionMenu.py +++ b/direct/src/gui/DirectOptionMenu.py @@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton): # List of items to display on the popup menu ('items', [], self.setItems), # Initial item to display on menu button - # Can be an interger index or the same string as the button + # Can be an integer index or the same string as the button ('initialitem', None, DGG.INITOPT), # Amount of padding to place around popup button indicator ('popupMarkerBorder', (.1, .1), None), + # The initial position of the popup marker + ('popupMarker_pos', (0, 0, 0), None), # Background color to use to highlight popup menu items ('highlightColor', (.5, .5, .5, 1), None), # Extra scale to use on highlight popup menu items @@ -42,6 +44,8 @@ class DirectOptionMenu(DirectButton): DirectButton.__init__(self, parent) # Record any user specified frame size self.initFrameSize = self['frameSize'] + # Record any user specified popup marker position + self.initPopupMarkerPos = self['popupMarker_pos'] # Create a small rectangular marker to distinguish this button # as a popup menu button self.popupMarker = self.createcomponent( @@ -168,8 +172,13 @@ class DirectOptionMenu(DirectButton): else: # Or base it upon largest item bounds = [self.minX, self.maxX, self.minZ, self.maxZ] - pm.setPos(bounds[1] + pmw/2.0, 0, - bounds[2] + (bounds[3] - bounds[2])/2.0) + if self.initPopupMarkerPos: + # Use specified position + pmPos = list(self.initPopupMarkerPos) + else: + # Or base the position on the frame size. + pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0] + pm.setPos(pmPos[0], pmPos[1], pmPos[2]) # Adjust popup menu button to fit all items (or use user specified # frame size bounds[1] += pmw @@ -184,6 +193,12 @@ class DirectOptionMenu(DirectButton): Adjust popup position if default position puts it outside of visible screen region """ + + # Needed attributes (such as minZ) won't be set unless the user has specified + # items to display. Let's assert that we've given items to work with. + items = self['items'] + assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!' + # Show the menu self.popupMenu.show() # Make sure its at the right scale From f32dc3cf2b02ccb02bc6e66c09af9cfee50df3cd Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 16:17:17 +0200 Subject: [PATCH 37/77] tkpanels: add missing AnimPanel imports --- direct/src/tkpanels/AnimPanel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/direct/src/tkpanels/AnimPanel.py b/direct/src/tkpanels/AnimPanel.py index 8cee51e1a6..ee61d9404e 100644 --- a/direct/src/tkpanels/AnimPanel.py +++ b/direct/src/tkpanels/AnimPanel.py @@ -9,13 +9,16 @@ __all__ = ['AnimPanel', 'ActorControl'] # Import Tkinter, Pmw, and the floater code from this directory tree. from direct.tkwidgets.AppShell import * from direct.showbase.TkGlobal import * -import Pmw, sys +import Pmw, sys, os from direct.task import Task +from panda3d.core import Filename, getModelPath if sys.version_info >= (3, 0): from tkinter.simpledialog import askfloat + from tkinter.filedialog import askopenfilename else: from tkSimpleDialog import askfloat + from tkFileDialog import askopenfilename FRAMES = 0 From a3b4486ef3b5059b6623fae64c336e8f9f088253 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 16:17:43 +0200 Subject: [PATCH 38/77] tkpanels: fix a few exceptions in AnimPanel --- direct/src/tkpanels/AnimPanel.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/direct/src/tkpanels/AnimPanel.py b/direct/src/tkpanels/AnimPanel.py index ee61d9404e..a8348c299d 100644 --- a/direct/src/tkpanels/AnimPanel.py +++ b/direct/src/tkpanels/AnimPanel.py @@ -276,7 +276,7 @@ class AnimPanel(AppShell): title = 'Load Animation', parent = self.component('hull') ) - if (animFilename == ''): + if not animFilename: # no file selected, canceled return @@ -372,8 +372,9 @@ class AnimPanel(AppShell): def destroy(self): # First clean up taskMgr.remove(self.id + '_UpdateTask') - self.destroyCallBack() - self.destroyCallBack = None + if self.destroyCallBack is not None: + self.destroyCallBack() + self.destroyCallBack = None AppShell.destroy(self) class ActorControl(Pmw.MegaWidget): From 6d9b217c2c0782265fb2cf5822171d092c9ccb13 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 16:18:22 +0200 Subject: [PATCH 39/77] tkwidgets: fix exceptions hovering over rgbPanel menu items --- direct/src/tkwidgets/Valuator.py | 49 +++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/direct/src/tkwidgets/Valuator.py b/direct/src/tkwidgets/Valuator.py index 39e0c8d73f..1704948571 100644 --- a/direct/src/tkwidgets/Valuator.py +++ b/direct/src/tkwidgets/Valuator.py @@ -656,25 +656,35 @@ def rgbPanel(nodePath, callback = None, style = 'mini'): pButton.pack(expand = 1, fill = BOTH) # Update menu - menu = vgp.component('menubar').component('Valuator Group-menu') + menubar = vgp.component('menubar') + menubar.deletemenuitems('Valuator Group', 1, 1) + # Some helper functions # Clear color - menu.insert_command(index = 1, label = 'Clear Color', - command = lambda: nodePath.clearColor()) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Clear Color', command=lambda: nodePath.clearColor()) # Set Clear Transparency - menu.insert_command(index = 2, label = 'Set Transparency', - command = lambda: nodePath.setTransparency(1)) - menu.insert_command( - index = 3, label = 'Clear Transparency', - command = lambda: nodePath.clearTransparency()) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Set Transparency', command=lambda: nodePath.setTransparency(1)) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Clear Transparency', command=lambda: nodePath.clearTransparency()) # System color picker - menu.insert_command(index = 4, label = 'Popup Color Picker', - command = popupColorPicker) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Popup Color Picker', command=popupColorPicker) - menu.insert_command(index = 5, label = 'Print to log', - command = printToLog) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Print to log', command=printToLog) + + menubar.addmenuitem( + 'Valuator Group', 'command', 'Dismiss Valuator Group panel', + label='Dismiss', command=vgp.destroy) def setNodePathColor(color): nodePath.setColor(color[0]/255.0, color[1]/255.0, @@ -724,18 +734,23 @@ def lightRGBPanel(light, style = 'mini'): # Update menu button vgp.component('menubar').component('Valuator Group-button')['text'] = ( 'Light Control Panel') + # Add a print button which will also serve as a color tile pButton = Button(vgp.interior(), text = 'Print to Log', bg = getTkColorString(initColor), command = printToLog) pButton.pack(expand = 1, fill = BOTH) + # Update menu - menu = vgp.component('menubar').component('Valuator Group-menu') + menubar = vgp.component('menubar') # System color picker - menu.insert_command(index = 4, label = 'Popup Color Picker', - command = popupColorPicker) - menu.insert_command(index = 5, label = 'Print to log', - command = printToLog) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Popup Color Picker', command=popupColorPicker) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Print to log', command=printToLog) + def setLightColor(color): light.setColor(Vec4(color[0]/255.0, color[1]/255.0, color[2]/255.0, color[3]/255.0)) From cc4d5259ccc6f5fd97dee2947adf605fe7999704 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 19:40:23 +0200 Subject: [PATCH 40/77] linmath: fix mat4.get_col3() and mat4.get_row3() when using Eigen --- panda/src/linmath/lmatrix4_src.I | 8 ------- tests/linmath/test_lmatrix4.py | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/panda/src/linmath/lmatrix4_src.I b/panda/src/linmath/lmatrix4_src.I index a887d393f1..fe8ba22bb7 100644 --- a/panda/src/linmath/lmatrix4_src.I +++ b/panda/src/linmath/lmatrix4_src.I @@ -484,13 +484,9 @@ get_col(int col) const { */ INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4):: get_row3(int row) const { -#ifdef HAVE_EIGEN - return FLOATNAME(LVecBase3)(_m.block<1, 3>(row, 0)); -#else return FLOATNAME(LVecBase3)((*this)(row, 0), (*this)(row, 1), (*this)(row, 2)); -#endif // HAVE_EIGEN } /** @@ -514,13 +510,9 @@ get_row3(FLOATNAME(LVecBase3) &result_vec,int row) const { */ INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4):: get_col3(int col) const { -#ifdef HAVE_EIGEN - return FLOATNAME(LVecBase3)(_m.block<1, 3>(0, col)); -#else return FLOATNAME(LVecBase3)((*this)(0, col), (*this)(1, col), (*this)(2, col)); -#endif // HAVE_EIGEN } /** diff --git a/tests/linmath/test_lmatrix4.py b/tests/linmath/test_lmatrix4.py index 23c1bcc5e9..05c487f807 100644 --- a/tests/linmath/test_lmatrix4.py +++ b/tests/linmath/test_lmatrix4.py @@ -87,3 +87,39 @@ def test_mat4_invert_correct(type): assert (mat * inv).is_identity() assert (inv * mat).is_identity() + + +@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f)) +def test_mat4_rows(type): + mat = type((1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16)) + + assert mat.rows[0] == (1, 2, 3, 4) + assert mat.rows[1] == (5, 6, 7, 8) + assert mat.rows[2] == (9, 10, 11, 12) + assert mat.rows[3] == (13, 14, 15, 16) + + assert mat.get_row3(0) == (1, 2, 3) + assert mat.get_row3(1) == (5, 6, 7) + assert mat.get_row3(2) == (9, 10, 11) + assert mat.get_row3(3) == (13, 14, 15) + + +@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f)) +def test_mat4_cols(type): + mat = type((1, 5, 9, 13, + 2, 6, 10, 14, + 3, 7, 11, 15, + 4, 8, 12, 16)) + + assert mat.cols[0] == (1, 2, 3, 4) + assert mat.cols[1] == (5, 6, 7, 8) + assert mat.cols[2] == (9, 10, 11, 12) + assert mat.cols[3] == (13, 14, 15, 16) + + assert mat.get_col3(0) == (1, 2, 3) + assert mat.get_col3(1) == (5, 6, 7) + assert mat.get_col3(2) == (9, 10, 11) + assert mat.get_col3(3) == (13, 14, 15) From 7f0ac22ca5949b64603546f26aa5c20457fa0693 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 21:29:03 +0200 Subject: [PATCH 41/77] device: fix mappings for various generic gamepads on Windows See also #576 --- panda/src/device/winRawInputDevice.cxx | 49 ++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index e2e27761bc..10262d124c 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -32,6 +32,12 @@ enum QuirkBits : int { // Throttle is reversed. QB_reversed_throttle = 4, + + // Right stick uses Z and Rz inputs. + QB_rstick_from_z = 8, + + // Axes on the right stick are swapped, using x for y and vice versa. + QB_right_axes_swapped = 64, }; // Some nonstandard gamepads have different button mappings. @@ -42,12 +48,17 @@ static const struct DeviceMapping { int quirks; const char *buttons[16]; } mapping_presets[] = { - // SNES-style USB gamepad + // SNES-style USB gamepad, or cheap unbranded USB gamepad with no sticks + // ABXY are mapped based on their position, not based on their label. {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, - {"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", "none", "none", "back", "start"} + {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start"} }, - // SPEED Link SL-6535-SBK-01 - {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, + // Unbranded generic cheap USB gamepad + {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers | QB_right_axes_swapped, + {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"} + }, + // Trust GXT 24 / SPEED Link SL-6535-SBK-01 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers, {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"} }, // T.Flight Hotas X @@ -56,7 +67,7 @@ static const struct DeviceMapping { }, // NVIDIA Shield Controller {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0, - {"face_a", "face_b", "n", "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", "e", "f", "g", "start", "h", "lstick", "rstick", "i"} + {"face_a", "face_b", 0, "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", 0, 0, 0, "start", 0, "lstick", "rstick", 0} }, {0}, }; @@ -422,7 +433,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_Z: if (_device_class == DeviceClass::gamepad) { - if ((quirks & QB_no_analog_triggers) == 0) { + if (quirks & QB_rstick_from_z) { + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_y; + swap(cap.LogicalMin, cap.LogicalMax); + } else { + axis = InputDevice::Axis::right_x; + } + } else if ((quirks & QB_no_analog_triggers) == 0) { axis = Axis::left_trigger; } } else if (_device_class == DeviceClass::flight_stick) { @@ -455,7 +473,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_RZ: if (_device_class == DeviceClass::gamepad) { - if ((quirks & QB_no_analog_triggers) == 0) { + if (quirks & QB_rstick_from_z) { + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_x; + } else { + axis = InputDevice::Axis::right_y; + swap(cap.LogicalMin, cap.LogicalMax); + } + } else if ((quirks & QB_no_analog_triggers) == 0) { axis = Axis::right_trigger; } } else { @@ -481,6 +506,16 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; } + // If this axis already exists, don't double-map it, but take the first + // one. This is important for the Trust GXT 24 / SL-6535-SBK-01 which + // have a weird extra Z axis with DataIndex 2 that should be ignored. + for (size_t i = 0; i < _axes.size(); ++i) { + if (_axes[i].axis == axis) { + axis = Axis::none; + break; + } + } + int axis_index; if (!is_signed) { // All axes on the weird XInput-style mappings go from -1 to 1 From 71eaa65e4ee4c026578f1e6eab8c64dd56317526 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 16:52:50 +0200 Subject: [PATCH 42/77] movies: fix another streampos comparison error in VC2015 --- panda/src/movies/opusAudioCursor.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index 3aee7c5844..ab81068742 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -194,7 +194,7 @@ seek(double t) { // be able to explicitly seek to the beginning of the file and call op_open // again. This allows looping compressed .opus files. if (error == OP_ENOSEEK && sample == 0) { - if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == 0) { + if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { OggOpusFile *op = op_open_callbacks((void *)_stream, &callbacks, nullptr, 0, nullptr); if (op != nullptr) { op_free(_op); From 362ee33d32fb12c45010756e45c028f5d94780c5 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 17:44:27 +0200 Subject: [PATCH 43/77] tests: hopefully make notify_all() test more robust --- tests/pipeline/test_condition_var.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/pipeline/test_condition_var.py b/tests/pipeline/test_condition_var.py index 6aa8bcf320..3458d1c9b6 100644 --- a/tests/pipeline/test_condition_var.py +++ b/tests/pipeline/test_condition_var.py @@ -123,12 +123,16 @@ def test_cvar_notify_all_threads(num_threads): break assert state['waiting'] == num_threads - m.release() # OK, now signal it, and yield. All threads must unblock. cv.notify_all() - yield_thread() - m.acquire() + for i in range(1000): + m.release() + yield_thread() + m.acquire() + if state['waiting'] == 0: + break + assert state['waiting'] == 0 m.release() From 6e7739354d60e88f63791c2d57345709eb554a9f Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 19:11:50 +0200 Subject: [PATCH 44/77] makepanda: add required IOKit and Quartz framework dependencies --- makepanda/makepanda.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 4b20b479df..751138bf30 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1009,6 +1009,8 @@ if (COMPILER=="GCC"): if GetTarget() == 'darwin': LibName("ALWAYS", "-framework AppKit") + LibName("IOKIT", "-framework IOKit") + LibName("QUARTZ", "-framework Quartz") LibName("AGL", "-framework AGL") LibName("CARBON", "-framework Carbon") LibName("COCOA", "-framework Cocoa") @@ -4203,7 +4205,7 @@ if (not RUNTIME): OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ', 'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', 'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI', - 'SETUPAPI'] + 'SETUPAPI', 'IOKIT'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') @@ -4842,7 +4844,7 @@ if (GetTarget() == 'darwin' and PkgSkip("COCOA")==0 and PkgSkip("GL")==0 and not if (PkgSkip('PANDAFX')==0): TargetAdd('libpandagl.dll', input='libpandafx.dll') TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS) - TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON']) + TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON', 'QUARTZ']) # # DIRECTORY: panda/src/wgldisplay/ From 81c87ef989e37fe208d6f94a00dcc2296e2050ed Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 2 May 2019 21:19:27 +0200 Subject: [PATCH 45/77] makepanda: only pass -undefined dynamic_lookup for Python modules --- makepanda/makepanda.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 751138bf30..78dfc98cc0 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1017,6 +1017,14 @@ if (COMPILER=="GCC"): # Fix for a bug in OSX Leopard: LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib") + # Temporary exceptions to removal of this flag + if not PkgSkip("ROCKET"): + LibName("ROCKET", "-undefined dynamic_lookup") + if not PkgSkip("FFMPEG"): + LibName("FFMPEG", "-undefined dynamic_lookup") + if not PkgSkip("ASSIMP"): + LibName("ASSIMP", "-undefined dynamic_lookup") + if GetTarget() == 'android': LibName("ALWAYS", '-llog') LibName("ANDROID", '-landroid') @@ -1142,6 +1150,7 @@ def BracketNameWithQuotes(name): # Workaround for OSX bug - compiler doesn't like those flags quoted. if (name.startswith("-framework")): return name if (name.startswith("-dylib_file")): return name + if (name.startswith("-undefined ")): return name # Don't add quotes when it's not necessary. if " " not in name: return name @@ -1815,9 +1824,11 @@ def CompileLink(dll, obj, opts): cmd += ' -Wl,--allow-shlib-undefined' else: if (GetTarget() == "darwin"): - cmd = cxx + ' -undefined dynamic_lookup' - if ("BUNDLE" in opts or GetOrigExt(dll) == ".pyd"): - cmd += ' -bundle ' + cmd = cxx + if GetOrigExt(dll) == ".pyd": + cmd += ' -bundle -undefined dynamic_lookup' + elif "BUNDLE" in opts: + cmd += ' -bundle' else: install_name = '@loader_path/../lib/' + os.path.basename(dll) cmd += ' -dynamiclib -install_name ' + install_name From 53612512d572e132af2fb45e4005afbe10b7bf7a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 14:33:46 +0200 Subject: [PATCH 46/77] glgsg: more reliable check for core/compat profile Fixes #643 --- .../glstuff/glGraphicsStateGuardian_src.cxx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 6151a2c00d..3ad8e01428 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -597,8 +597,31 @@ reset() { query_glsl_version(); #ifndef OPENGLES - bool core_profile = is_at_least_gl_version(3, 2) && - !has_extension("GL_ARB_compatibility"); + // Determine whether this OpenGL context has compatibility features. + bool core_profile = false; + + if (_gl_version_major >= 3) { + if (_gl_version_major > 3 || _gl_version_minor >= 2) { + // OpenGL 3.2 has a built-in way to check this. + GLint profile_mask = 0; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile_mask); + + if (profile_mask & GL_CONTEXT_CORE_PROFILE_BIT) { + core_profile = true; + } else if (profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) { + core_profile = false; + } else { + core_profile = !has_extension("GL_ARB_compatibility"); + } + } else { + // OpenGL 3.0/3.1. + GLint flags = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &flags); + if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) { + core_profile = true; + } + } + } if (GLCAT.is_debug()) { if (core_profile) { From 475bd55bb1a9fcdaaf68c02dabcc6ed2370f52f4 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 15:46:01 +0200 Subject: [PATCH 47/77] glgsg: add gl-forward-compatible config variable This is meant to be used alongside gl-version to request a "forward compatible" OpenGL 3.0 or 3.1 context, which removes support for deprecated features such as the fixed-function pipeline. --- panda/src/glstuff/glmisc_src.cxx | 5 +++++ panda/src/glstuff/glmisc_src.h | 1 + panda/src/glxdisplay/glxGraphicsStateGuardian.cxx | 13 ++++++++++++- panda/src/wgldisplay/wglGraphicsStateGuardian.cxx | 13 ++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/panda/src/glstuff/glmisc_src.cxx b/panda/src/glstuff/glmisc_src.cxx index 08e200a4a0..49eaf78a69 100644 --- a/panda/src/glstuff/glmisc_src.cxx +++ b/panda/src/glstuff/glmisc_src.cxx @@ -17,6 +17,11 @@ ConfigVariableInt gl_version ("gl-version", "", PRC_DESC("Set this to get an OpenGL context with a specific version.")); +ConfigVariableBool gl_forward_compatible + ("gl-forward-compatible", false, + PRC_DESC("Setting this to true will request a forward-compatible OpenGL " + "context, which will not support the fixed-function pipeline.")); + ConfigVariableBool gl_support_fbo ("gl-support-fbo", true, PRC_DESC("Configure this false if your GL's implementation of " diff --git a/panda/src/glstuff/glmisc_src.h b/panda/src/glstuff/glmisc_src.h index 3c6dd81e7b..415286c674 100644 --- a/panda/src/glstuff/glmisc_src.h +++ b/panda/src/glstuff/glmisc_src.h @@ -41,6 +41,7 @@ // #define GSG_VERBOSE 1 extern EXPCL_GL ConfigVariableInt gl_version; +extern EXPCL_GL ConfigVariableBool gl_forward_compatible; extern EXPCL_GL ConfigVariableBool gl_support_fbo; extern ConfigVariableBool gl_cheap_textures; extern ConfigVariableBool gl_ignore_clamp; diff --git a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx index d620427afa..83ea5f2b2f 100644 --- a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx +++ b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx @@ -352,9 +352,20 @@ choose_pixel_format(const FrameBufferProperties &properties, attrib_list[n++] = gl_version[1]; } } + int flags = 0; if (gl_debug) { + flags |= GLX_CONTEXT_DEBUG_BIT_ARB; + } + if (gl_forward_compatible) { + flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; + if (gl_version.get_num_words() == 0 || gl_version[0] < 2) { + glxdisplay_cat.error() + << "gl-forward-compatible requires gl-version >= 3 0\n"; + } + } + if (flags != 0) { attrib_list[n++] = GLX_CONTEXT_FLAGS_ARB; - attrib_list[n++] = GLX_CONTEXT_DEBUG_BIT_ARB; + attrib_list[n++] = flags; } attrib_list[n] = None; _context = _glXCreateContextAttribs(_display, _fbconfig, _share_context, diff --git a/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx b/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx index c98f12cf48..ed9b0e0b2c 100644 --- a/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx +++ b/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx @@ -609,9 +609,20 @@ make_context(HDC hdc) { attrib_list[n++] = gl_version[1]; } } + int flags = 0; if (gl_debug) { + flags |= WGL_CONTEXT_DEBUG_BIT_ARB; + } + if (gl_forward_compatible) { + flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; + if (gl_version.get_num_words() == 0 || gl_version[0] < 2) { + wgldisplay_cat.error() + << "gl-forward-compatible requires gl-version >= 3 0\n"; + } + } + if (flags != 0) { attrib_list[n++] = WGL_CONTEXT_FLAGS_ARB; - attrib_list[n++] = WGL_CONTEXT_DEBUG_BIT_ARB; + attrib_list[n++] = flags; } #ifndef SUPPORT_FIXED_FUNCTION attrib_list[n++] = WGL_CONTEXT_PROFILE_MASK_ARB; From b08e38cf3dfcb2bcfdfad46665202548179a5423 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 16:12:20 +0200 Subject: [PATCH 48/77] deploy-ng: add nag screen warning users who are still on Python 2 See #602 --- direct/src/dist/commands.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 948891d19d..9ba567f067 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -13,6 +13,7 @@ import stat import struct import imp import string +import time import setuptools import distutils.log @@ -30,6 +31,15 @@ if sys.version_info < (3, 0): # Python 3 defines these subtypes of IOError, but Python 2 doesn't. FileNotFoundError = IOError + # Warn the user. They might be using Python 2 by accident. + print("=================================================================") + print("WARNING: You are using Python 2, which will soon be discontinued.") + print("WARNING: Please use Python 3 for best results and continued") + print("WARNING: support after the EOL date of December 31st, 2019.") + print("=================================================================") + sys.stdout.flush() + time.sleep(4.0) + def _parse_list(input): if isinstance(input, basestring): From ea0210640cebe63db93333d13ead2e33ddaff188 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 15:48:51 +0200 Subject: [PATCH 49/77] FilterManager: allow specifying custom fbprops in renderSceneInto Fixes #599 --- direct/src/filter/FilterManager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/direct/src/filter/FilterManager.py b/direct/src/filter/FilterManager.py index 4150cba568..5696ac9460 100644 --- a/direct/src/filter/FilterManager.py +++ b/direct/src/filter/FilterManager.py @@ -124,7 +124,7 @@ class FilterManager(DirectObject): return winx,winy - def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None): + def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None): """ Causes the scene to be rendered into the supplied textures instead of into the original window. Puts a fullscreen quad @@ -185,7 +185,10 @@ class FilterManager(DirectObject): # Choose the size of the offscreen buffer. (winx, winy) = self.getScaledSize(1,1,1) - buffer = self.createBuffer("filter-base", winx, winy, texgroup) + if fbprops is not None: + buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops) + else: + buffer = self.createBuffer("filter-base", winx, winy, texgroup) if (buffer == None): return None @@ -287,7 +290,7 @@ class FilterManager(DirectObject): return quad - def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1): + def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None): """ Low-level buffer creation. Not intended for public use. """ winprops = WindowProperties() @@ -297,6 +300,9 @@ class FilterManager(DirectObject): props.setRgbColor(1) props.setDepthBits(depthbits) props.setStereo(self.win.isStereo()) + if fbprops is not None: + props.addProperties(fbprops) + depthtex, colortex, auxtex0, auxtex1 = texgroup if (auxtex0 != None): props.setAuxRgba(1) From 541a2a73f0e10ef89d177c742ea4fa48c43c7325 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 15:51:31 +0200 Subject: [PATCH 50/77] showbase: allow attaching default MouseWatcher in attachInputDevice This makes it easier to control GUIs using a gamepad. --- direct/src/showbase/ShowBase.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index bff3b4652c..c0e6d9dd16 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1677,13 +1677,17 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) - def attachInputDevice(self, device, prefix=None): + def attachInputDevice(self, device, prefix=None, gui=False): """ This function attaches an input device to the data graph, which will cause the device to be polled and generate events. If a prefix is given and not None, it is used to prefix events generated by this device, separated by a hyphen. + The gui argument can be set to True (as of Panda3D 1.10.3) to set up + the default MouseWatcher to receive inputs from this device, allowing + it to control user interfaces. + If you call this, you should consider calling detachInputDevice when you are done with the device or when it is disconnected. """ @@ -1694,13 +1698,17 @@ class ShowBase(DirectObject.DirectObject): idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name)) # Setup the button thrower to generate events for the device. - bt = idn.attachNewNode(ButtonThrower(device.name)) - if prefix is not None: - bt.node().setPrefix(prefix + '-') + if prefix is not None or not gui: + bt = idn.attachNewNode(ButtonThrower(device.name)) + if prefix is not None: + bt.node().setPrefix(prefix + '-') + self.deviceButtonThrowers.append(bt) assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix)) self.__inputDeviceNodes[device] = idn - self.deviceButtonThrowers.append(bt) + + if gui: + idn.node().addChild(self.mouseWatcherNode) def detachInputDevice(self, device): """ From db00baa230f57486043f2ca33267e665de6d5b8d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 17:52:08 +0200 Subject: [PATCH 51/77] deploy-ng: add link to index for thirdparty wheels This is where we can host wheels for packages that haven't uploaded wheels for all platforms, such as PyYAML and esper. [skip ci] --- direct/src/dist/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 9ba567f067..e5d9ac4f72 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -239,7 +239,9 @@ class build_apps(setuptools.Command): self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt') self.use_optimized_wheels = True self.optimized_wheel_index = '' - self.pypi_extra_indexes = [] + self.pypi_extra_indexes = [ + 'https://archive.panda3d.org/thirdparty', + ] self.file_handlers = {} self.exclude_dependencies = [ # Windows From d7f89bd3a42b1365c29c5e8ae86d1fa9d9590e5c Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 18:02:19 +0200 Subject: [PATCH 52/77] makepanda: also use -undefined dynamic_lookup for OpenEXR for now --- makepanda/makepanda.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 78dfc98cc0..59baef8e7c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1024,6 +1024,8 @@ if (COMPILER=="GCC"): LibName("FFMPEG", "-undefined dynamic_lookup") if not PkgSkip("ASSIMP"): LibName("ASSIMP", "-undefined dynamic_lookup") + if not PkgSkip("OPENEXR"): + LibName("OPENEXR", "-undefined dynamic_lookup") if GetTarget() == 'android': LibName("ALWAYS", '-llog') From 226d888ef49bf8458cdc40e7ec7fdee638e22ee8 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 18:02:45 +0200 Subject: [PATCH 53/77] collide: remove doubly defined property due to faulty merge --- panda/src/collide/collisionTraverser.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index df7f4b40ce..a51a1bd474 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -51,8 +51,6 @@ PUBLISHED: INLINE bool get_respect_prev_transform() const; MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform, set_respect_prev_transform); - MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform, - set_respect_prev_transform); void add_collider(const NodePath &collider, CollisionHandler *handler); bool remove_collider(const NodePath &collider); From bf302a08389c3ecb61b8867dbbc120cd251f598a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 18:03:16 +0200 Subject: [PATCH 54/77] tests: hopefully fix sporadic test failures with condition var test --- tests/pipeline/test_condition_var.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/pipeline/test_condition_var.py b/tests/pipeline/test_condition_var.py index 3458d1c9b6..38c7c4cf25 100644 --- a/tests/pipeline/test_condition_var.py +++ b/tests/pipeline/test_condition_var.py @@ -72,16 +72,22 @@ def test_cvar_notify_thread(num_threads): break assert state['waiting'] == num_threads - m.release() # OK, now signal it, and yield. One thread must be unblocked per notify. for i in range(num_threads): cv.notify() - yield_thread() - m.acquire() - assert state['waiting'] == num_threads - i - 1 - m.release() + expected_waiters = num_threads - i - 1 + for j in range(1000): + m.release() + yield_thread() + m.acquire() + if state['waiting'] == expected_waiters: + break + + assert state['waiting'] == expected_waiters + + m.release() for thread in threads: thread.join() cv = None From 54c6eaeb960a404235d87f6d471dce95c1c9879c Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 19:21:18 +0200 Subject: [PATCH 55/77] pgui: allow keyboard keys to be added as PGButton click buttons These will respond as clicks not when the mouse cursor is hovering over them, but when they have keyboard focus. Fixes #600 --- panda/src/pgui/pgButton.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/panda/src/pgui/pgButton.cxx b/panda/src/pgui/pgButton.cxx index d1eba186c0..09a91120d4 100644 --- a/panda/src/pgui/pgButton.cxx +++ b/panda/src/pgui/pgButton.cxx @@ -115,7 +115,11 @@ release(const MouseWatcherParameter ¶m, bool background) { if (has_click_button(param.get_button())) { _button_down = false; if (get_active()) { - if (param.is_outside()) { + // Note that a "click" may come from a keyboard button press. In that + // case, instead of checking that the mouse cursor is still over the + // button, we check whether the item has keyboard focus. + if (param.is_outside() && + (MouseButton::is_mouse_button(param.get_button()) || !get_focus())) { set_state(S_ready); } else { set_state(S_rollover); From 0568312324932552619138f9a9c8beb366c519e3 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 19:41:27 +0200 Subject: [PATCH 56/77] windisplay: add config var to disable Ctrl+V behaviour Fixes #512 --- panda/src/windisplay/config_windisplay.cxx | 5 +++++ panda/src/windisplay/config_windisplay.h | 1 + panda/src/windisplay/winGraphicsWindow.cxx | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/panda/src/windisplay/config_windisplay.cxx b/panda/src/windisplay/config_windisplay.cxx index 6e0ea2406d..62040c553a 100644 --- a/panda/src/windisplay/config_windisplay.cxx +++ b/panda/src/windisplay/config_windisplay.cxx @@ -86,6 +86,11 @@ ConfigVariableBool swapbuffer_framelock ("swapbuffer-framelock", false, PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards")); +ConfigVariableBool paste_emit_keystrokes +("paste-emit-keystrokes", true, + PRC_DESC("Handle paste events (Ctrl-V) as separate keystroke events for each " + "pasted character.")); + /** * 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/windisplay/config_windisplay.h b/panda/src/windisplay/config_windisplay.h index 3256287f5b..7199b8603d 100644 --- a/panda/src/windisplay/config_windisplay.h +++ b/panda/src/windisplay/config_windisplay.h @@ -31,6 +31,7 @@ extern ConfigVariableBool ime_hide; extern ConfigVariableBool request_dxdisplay_information; extern ConfigVariableBool dpi_aware; extern ConfigVariableBool dpi_window_resize; +extern ConfigVariableBool paste_emit_keystrokes; extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index d3e444cac9..c2e98beb0e 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -1927,7 +1927,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // Handle Cntrl-V paste from clipboard. Is there a better way to detect // this hotkey? if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) && - !_input_devices.empty()) { + !_input_devices.empty() && paste_emit_keystrokes) { HGLOBAL hglb; char *lptstr; From f25532db78127efd58df34a98d735fc6f9345ad2 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 20:48:51 +0200 Subject: [PATCH 57/77] glgsg: properly handle shader compilation failure Fixes #645 --- panda/src/glstuff/glShaderContext_src.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index 5a7eda65fc..200e39004d 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -3206,6 +3206,10 @@ glsl_compile_and_link() { valid &= glsl_compile_shader(Shader::ST_compute); } + if (!valid) { + return false; + } + // There might be warnings, so report those. GLSLShaders::const_iterator // it; for (it = _glsl_shaders.begin(); it != _glsl_shaders.end(); ++it) { // glsl_report_shader_errors(*it); } From fce282ea33df949d58dd5d8b354bb4820056370e Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 May 2019 22:04:24 +0200 Subject: [PATCH 58/77] Emit warning when importing panda3d using Python 2.7 Fixes #602 --- makepanda/makepanda.py | 8 ++++++++ makepanda/makewheel.py | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 59baef8e7c..4efcc47fda 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2880,6 +2880,14 @@ for basename in del_files: p3d_init = """"Python bindings for the Panda3D libraries" __version__ = '%s' + +if __debug__: + import sys + if sys.version_info < (3, 0): + sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n") + sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n") + sys.stderr.flush() + del sys """ % (WHLVERSION) if GetTarget() == 'windows': diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index fea29bd17f..e7e143c264 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -596,10 +596,23 @@ def makewheel(version, output_dir, platform=None): # Write the panda3d tree. We use a custom empty __init__ since the # default one adds the bin directory to the PATH, which we don't have. - whl.write_file_data('panda3d/__init__.py', """"Python bindings for the Panda3D libraries" + p3d_init = """"Python bindings for the Panda3D libraries" __version__ = '{0}' -""".format(version)) +""".format(version) + + if '27' in ABI_TAG: + p3d_init += """ +if __debug__: + import sys + if sys.version_info < (3, 0): + sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n") + sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n") + sys.stderr.flush() + del sys +""" + + whl.write_file_data('panda3d/__init__.py', p3d_init) # Copy the extension modules from the panda3d directory. ext_suffix = GetExtensionSuffix() From 11808862f2d3ecad1b4600f1526e03ef8ce28f7c Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 9 May 2019 18:44:13 +0200 Subject: [PATCH 59/77] showbase: fix BufferViewer when main window is opened later Fixes #648 --- direct/src/showbase/ShowBase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index c0e6d9dd16..ddb7fb48d8 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -829,9 +829,10 @@ class ShowBase(DirectObject.DirectObject): win.requestProperties(props) mainWindow = False - if self.win == None: + if self.win is None: mainWindow = True self.win = win + self.bufferViewer.win = win self.winList.append(win) From b8b6f2f2dcc7fcbdee76de3fb80c225d51a36292 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 9 May 2019 19:18:25 +0200 Subject: [PATCH 60/77] showbase: fix BufferViewer error when opening window right away --- direct/src/showbase/ShowBase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index ddb7fb48d8..5523d91a98 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -832,7 +832,8 @@ class ShowBase(DirectObject.DirectObject): if self.win is None: mainWindow = True self.win = win - self.bufferViewer.win = win + if hasattr(self, 'bufferViewer'): + self.bufferViewer.win = win self.winList.append(win) From 15cdd1da0a327453a308f432f40ec3a987704984 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 15:36:25 +0200 Subject: [PATCH 61/77] makepanda: also use -undefined dynamic_lookup for VRPN for now --- makepanda/makepanda.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 4efcc47fda..55b495b5b7 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1026,6 +1026,8 @@ if (COMPILER=="GCC"): LibName("ASSIMP", "-undefined dynamic_lookup") if not PkgSkip("OPENEXR"): LibName("OPENEXR", "-undefined dynamic_lookup") + if not PkgSkip("VRPN"): + LibName("VRPN", "-undefined dynamic_lookup") if GetTarget() == 'android': LibName("ALWAYS", '-llog') From 60922fabc18d76e76dcde4dd3b67e3f17652b936 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 15:38:35 +0200 Subject: [PATCH 62/77] Give istream/ostream a friendlier file-like interface for Python --- dtool/src/dtoolutil/iostream_ext.cxx | 317 ++++++++++++++++++ dtool/src/dtoolutil/iostream_ext.h | 53 +++ .../dtoolutil/p3dtoolutil_ext_composite.cxx | 1 + dtool/src/parser-inc/iostream | 39 ++- makepanda/makepanda.py | 1 + tests/dtoolutil/test_iostream.py | 128 +++++++ 6 files changed, 526 insertions(+), 13 deletions(-) create mode 100644 dtool/src/dtoolutil/iostream_ext.cxx create mode 100644 dtool/src/dtoolutil/iostream_ext.h create mode 100644 tests/dtoolutil/test_iostream.py diff --git a/dtool/src/dtoolutil/iostream_ext.cxx b/dtool/src/dtoolutil/iostream_ext.cxx new file mode 100644 index 0000000000..47f6140e59 --- /dev/null +++ b/dtool/src/dtoolutil/iostream_ext.cxx @@ -0,0 +1,317 @@ +/** + * 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 iostream_ext.cxx + * @author rdb + * @date 2017-07-24 + */ + +#include "iostream_ext.h" + +#ifdef HAVE_PYTHON + +#ifndef CPPPARSER +extern struct Dtool_PyTypedObject Dtool_std_istream; +#endif + +/** + * Reads the given number of bytes from the stream, returned as bytes object. + * If the given size is -1, all bytes are read from the stream. + */ +PyObject *Extension:: +read(int size) { + if (size < 0) { + return readall(); + } + + char *buffer; + std::streamsize read_bytes = 0; + + if (size > 0) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + buffer = (char *)alloca((size_t)size); + read_bytes = buf->sgetn(buffer, (size_t)size); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + } + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(buffer, read_bytes); +#else + return PyString_FromStringAndSize(buffer, read_bytes); +#endif +} + +/** + * Reads from the underlying stream, but using at most one call. The number + * of returned bytes may therefore be less than what was requested, but it + * will always be greater than 0 until EOF is reached. + */ +PyObject *Extension:: +read1(int size) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::streamsize avail = buf->in_avail(); + if (avail == 0) { + avail = 4096; + } + + if (size >= 0 && (std::streamsize)size < avail) { + avail = (std::streamsize)size; + } + + // Don't read more than 4K at a time + if (avail > 4096) { + avail = 4096; + } + + char *buffer = (char *)alloca(avail); + std::streamsize read_bytes = buf->sgetn(buffer, avail); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(buffer, read_bytes); +#else + return PyString_FromStringAndSize(buffer, read_bytes); +#endif +} + +/** + * Reads all of the bytes in the stream. + */ +PyObject *Extension:: +readall() { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::vector result; + + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + + std::streamsize count = buf->sgetn(buffer, buffer_size); + while (count != 0) { + thread_consider_yield(); + result.insert(result.end(), buffer, buffer + count); + count = buf->sgetn(buffer, buffer_size); + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize((char *)result.data(), result.size()); +#else + return PyString_FromStringAndSize((char *)result.data(), result.size()); +#endif +} + +/** + * Reads bytes into a preallocated, writable, bytes-like object, returning the + * number of bytes read. + */ +std::streamsize Extension:: +readinto(PyObject *b) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, 0); + + Py_buffer view; + if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG) == -1) { + PyErr_SetString(PyExc_TypeError, + "write() requires a contiguous, read-write bytes-like object"); + return 0; + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::streamsize count = buf->sgetn((char *)view.buf, view.len); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + + PyBuffer_Release(&view); + return count; +} + +/** + * Extracts one line up to and including the trailing newline character. + * Returns empty string when the end of file is reached. + */ +PyObject *Extension:: +readline(int size) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::string line; + int ch = buf->sbumpc(); + while (ch != EOF && (--size) != 0) { + line.push_back(ch); + if (ch == '\n') { + // Here's the newline character. + break; + } + ch = buf->sbumpc(); + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(line.data(), line.size()); +#else + return PyString_FromStringAndSize(line.data(), line.size()); +#endif +} + +/** + * Reads all the lines at once and returns a list. Also see the documentation + * for readline(). + */ +PyObject *Extension:: +readlines(int hint) { + PyObject *lst = PyList_New(0); + if (lst == nullptr) { + return nullptr; + } + + PyObject *py_line = readline(-1); + + if (hint < 0) { + while (Py_SIZE(py_line) > 0) { + PyList_Append(lst, py_line); + Py_DECREF(py_line); + + py_line = readline(-1); + } + } else { + size_t totchars = 0; + while (Py_SIZE(py_line) > 0) { + totchars += Py_SIZE(py_line); + PyList_Append(lst, py_line); + Py_DECREF(py_line); + + if (totchars > hint) { + break; + } + + py_line = readline(-1); + } + } + + return lst; +} + +/** + * Yields continuously to read all the lines from the istream. + */ +static PyObject *gen_next(PyObject *self) { + istream *stream = nullptr; + if (!Dtool_Call_ExtractThisPointer(self, Dtool_std_istream, (void **)&stream)) { + return nullptr; + } + + PyObject *line = invoke_extension(stream).readline(); + if (Py_SIZE(line) > 0) { + return line; + } else { + PyErr_SetObject(PyExc_StopIteration, nullptr); + return nullptr; + } +} + +/** + * Iterates over the lines of the file. + */ +PyObject *Extension:: +__iter__(PyObject *self) { + return Dtool_NewGenerator(self, &gen_next); +} + +/** + * Writes the bytes object to the stream. + */ +void Extension:: +write(PyObject *b) { + std::streambuf *buf = _this->rdbuf(); + nassertv(buf != nullptr); + + Py_buffer view; + if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG_RO) == -1) { + PyErr_SetString(PyExc_TypeError, "write() requires a contiguous buffer"); + return; + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + buf->sputn((const char *)view.buf, view.len); + Py_BLOCK_THREADS +#else + buf->sputn((const char *)view.buf, view.len); +#endif + + PyBuffer_Release(&view); +} + +/** + * Write a list of lines to the stream. Line separators are not added, so it + * is usual for each of the lines provided to have a line separator at the + * end. + */ +void Extension:: +writelines(PyObject *lines) { + PyObject *seq = PySequence_Fast(lines, "writelines() expects a sequence"); + if (seq == nullptr) { + return; + } + + PyObject **items = PySequence_Fast_ITEMS(seq); + Py_ssize_t len = PySequence_Fast_GET_SIZE(seq); + + for (Py_ssize_t i = 0; i < len; ++i) { + write(items[i]); + } + + Py_DECREF(seq); +} + +#endif // HAVE_PYTHON diff --git a/dtool/src/dtoolutil/iostream_ext.h b/dtool/src/dtoolutil/iostream_ext.h new file mode 100644 index 0000000000..e8413afa91 --- /dev/null +++ b/dtool/src/dtoolutil/iostream_ext.h @@ -0,0 +1,53 @@ +/** + * 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 iostream_ext.h + * @author rdb + * @date 2017-07-24 + */ + +#ifndef IOSTREAM_EXT_H +#define IOSTREAM_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include +#include "py_panda.h" + +/** + * These classes define the extension methods for istream and ostream, which + * are called instead of any C++ methods with the same prototype. + * + * These are designed to allow streams to be treated as file-like objects. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *read(int size=-1); + PyObject *read1(int size=-1); + PyObject *readall(); + std::streamsize readinto(PyObject *b); + + PyObject *readline(int size=-1); + PyObject *readlines(int hint=-1); + PyObject *__iter__(PyObject *self); +}; + +template<> +class Extension : public ExtensionBase { +public: + void write(PyObject *b); + void writelines(PyObject *lines); +}; + +#endif // HAVE_PYTHON + +#endif // IOSTREAM_EXT_H diff --git a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx index 2cd825ff58..ceb2f01bf1 100644 --- a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx +++ b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx @@ -1,3 +1,4 @@ #include "filename_ext.cxx" #include "globPattern_ext.cxx" +#include "iostream_ext.cxx" #include "textEncoder_ext.cxx" diff --git a/dtool/src/parser-inc/iostream b/dtool/src/parser-inc/iostream index 8bfe91d0aa..c6c2edffab 100644 --- a/dtool/src/parser-inc/iostream +++ b/dtool/src/parser-inc/iostream @@ -1,16 +1,15 @@ -// Filename: iostream -// Created by: drose (12May00) -// -//////////////////////////////////////////////////////////////////// -// -// 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." -// -//////////////////////////////////////////////////////////////////// +/** + * 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 iostream + * @author drose + * @date 2000-05-12 + */ // This file, and all the other files in this directory, aren't // intended to be compiled--they're just parsed by CPPParser (and @@ -34,6 +33,9 @@ namespace std { __published: ostream(const ostream&) = delete; + __extension void write(PyObject *b); + __extension void writelines(PyObject *lines); + void put(char c); void flush(); streampos tellp(); @@ -43,10 +45,20 @@ namespace std { protected: ostream(ostream &&); }; + class istream : virtual public ios { __published: istream(const istream&) = delete; + __extension PyObject *read(int size=-1); + __extension PyObject *read1(int size=-1); + __extension PyObject *readall(); + __extension std::streamsize readinto(PyObject *b); + + __extension PyObject *readline(int size=-1); + __extension PyObject *readlines(int hint=-1); + __extension PyObject *__iter__(PyObject *self); + int get(); streampos tellg(); void seekg(streampos pos); @@ -55,6 +67,7 @@ namespace std { protected: istream(istream &&); }; + class iostream : public istream, public ostream { __published: iostream(const iostream&) = delete; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 55b495b5b7..9941fa8175 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3685,6 +3685,7 @@ IGATEFILES += [ "globPattern_ext.h", "pandaFileStream.h", "lineStream.h", + "iostream_ext.h", ] TargetAdd('libp3dtoolutil.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3dtoolutil.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dtoolutil', 'SRCDIR:dtool/src/dtoolutil']) diff --git a/tests/dtoolutil/test_iostream.py b/tests/dtoolutil/test_iostream.py new file mode 100644 index 0000000000..2473ef80d2 --- /dev/null +++ b/tests/dtoolutil/test_iostream.py @@ -0,0 +1,128 @@ +from panda3d.core import StringStream + +import pytest + + +ISTREAM_DATA = b'abcdefghijklmnopqrstuvwxyz' * 500 + +@pytest.fixture +def istream(): + return StringStream(ISTREAM_DATA) + + +def test_istream_readall(istream): + assert istream.readall() == ISTREAM_DATA + assert istream.readall() == b'' + assert istream.readall() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read(istream): + assert istream.read() == ISTREAM_DATA + assert istream.read() == b'' + assert istream.read() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read_size(istream): + assert istream.read(100) == ISTREAM_DATA[:100] + assert istream.read(5000) == ISTREAM_DATA[100:5100] + assert istream.read(5000) == ISTREAM_DATA[5100:10100] + assert istream.read(5000) == ISTREAM_DATA[10100:15100] + assert istream.read() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read1(istream): + accumulated = b'' + data = istream.read1() + while data: + accumulated += data + data = istream.read1() + + assert accumulated == ISTREAM_DATA + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read1_size(istream): + accumulated = b'' + data = istream.read1(4000) + while data: + accumulated += data + data = istream.read1(4000) + + assert accumulated == ISTREAM_DATA + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_readinto(istream): + ba = bytearray() + assert istream.readinto(ba) == 0 + assert istream.tellg() == 0 + + ba = bytearray(10) + assert istream.readinto(ba) == 10 + assert ba == ISTREAM_DATA[:10] + assert istream.tellg() == 10 + + ba = bytearray(len(ISTREAM_DATA)) + assert istream.readinto(ba) == len(ISTREAM_DATA) - 10 + assert ba[:len(ISTREAM_DATA)-10] == ISTREAM_DATA[10:] + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_readline(): + # Empty stream + stream = StringStream(b'') + assert stream.readline() == b'' + assert stream.readline() == b'' + + # Single line without newline + stream = StringStream(b'A') + assert stream.readline() == b'A' + assert stream.readline() == b'' + + # Single newline + stream = StringStream(b'\n') + assert stream.readline() == b'\n' + assert stream.readline() == b'' + + # Line with text followed by empty line + stream = StringStream(b'A\n\n') + assert stream.readline() == b'A\n' + assert stream.readline() == b'\n' + assert stream.readline() == b'' + + # Preserve null byte + stream = StringStream(b'\x00\x00') + assert stream.readline() == b'\x00\x00' + + +def test_istream_readlines(): + istream = StringStream(b'a') + assert istream.readlines() == [b'a'] + assert istream.readlines() == [] + + istream = StringStream(b'a\nb\nc\n') + assert istream.readlines() == [b'a\n', b'b\n', b'c\n'] + + istream = StringStream(b'\na\nb\nc') + assert istream.readlines() == [b'\n', b'a\n', b'b\n', b'c'] + + istream = StringStream(b'\n\n\n') + assert istream.readlines() == [b'\n', b'\n', b'\n'] + + +def test_istream_iter(): + istream = StringStream(b'a') + assert tuple(istream) == (b'a',) + assert tuple(istream) == () + + istream = StringStream(b'a\nb\nc\n') + assert tuple(istream) == (b'a\n', b'b\n', b'c\n') + + istream = StringStream(b'\na\nb\nc') + assert tuple(istream) == (b'\n', b'a\n', b'b\n', b'c') + + istream = StringStream(b'\n\n\n') + assert tuple(istream) == (b'\n', b'\n', b'\n') From a7c743fd5eacf486fb6f462659d86579412de08a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 15:50:34 +0200 Subject: [PATCH 63/77] Allow seek of IDecryptStream to begin (for looping encrypted audio) --- dtool/src/prc/encryptStream.cxx | 20 +++++++++++ dtool/src/prc/encryptStream.h | 3 ++ dtool/src/prc/encryptStreamBuf.I | 18 ++++++++++ dtool/src/prc/encryptStreamBuf.cxx | 57 ++++++++++++++++++++++++++++-- dtool/src/prc/encryptStreamBuf.h | 9 +++++ panda/src/express/multifile.cxx | 5 +-- tests/prc/test_encrypt_stream.py | 19 ++++++++++ 7 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 tests/prc/test_encrypt_stream.py diff --git a/dtool/src/prc/encryptStream.cxx b/dtool/src/prc/encryptStream.cxx index 24656b7416..380296dd8c 100644 --- a/dtool/src/prc/encryptStream.cxx +++ b/dtool/src/prc/encryptStream.cxx @@ -12,3 +12,23 @@ */ #include "encryptStream.h" + +/** + * Must be called immediately after open_read(). Decrypts the given number of + * bytes and checks that they match. The amount of header bytes are added to + * an offset so that skipping to 0 will skip past the header. + * + * Returns true if the read magic matches the given magic, false on error. + */ +bool IDecryptStream:: +read_magic(const char *magic, size_t size) { + char this_magic[size]; + read(this_magic, size); + + if (!fail() && gcount() == size && memcmp(this_magic, magic, size) == 0) { + _buf.set_magic_length(size); + return true; + } else { + return false; + } +} diff --git a/dtool/src/prc/encryptStream.h b/dtool/src/prc/encryptStream.h index 94deaeb62e..6605ea5998 100644 --- a/dtool/src/prc/encryptStream.h +++ b/dtool/src/prc/encryptStream.h @@ -53,6 +53,9 @@ PUBLISHED: MAKE_PROPERTY(key_length, get_key_length); MAKE_PROPERTY(iteration_count, get_iteration_count); +public: + bool read_magic(const char *magic, size_t size); + private: EncryptStreamBuf _buf; }; diff --git a/dtool/src/prc/encryptStreamBuf.I b/dtool/src/prc/encryptStreamBuf.I index cbf9f59470..b840625fc1 100644 --- a/dtool/src/prc/encryptStreamBuf.I +++ b/dtool/src/prc/encryptStreamBuf.I @@ -81,3 +81,21 @@ INLINE int EncryptStreamBuf:: get_iteration_count() const { return _iteration_count; } + +/** + * Sets the amount of the encrypted data at the beginning that are skipped + * when seeking back to zero. + */ +INLINE void EncryptStreamBuf:: +set_magic_length(size_t length) { + _magic_length = length; +} + +/** + * Sets the amount of the encrypted data at the beginning that are skipped + * when seeking back to zero. + */ +INLINE size_t EncryptStreamBuf:: +get_magic_length() const { + return _magic_length; +} diff --git a/dtool/src/prc/encryptStreamBuf.cxx b/dtool/src/prc/encryptStreamBuf.cxx index fe562e59e6..b087c21fae 100644 --- a/dtool/src/prc/encryptStreamBuf.cxx +++ b/dtool/src/prc/encryptStreamBuf.cxx @@ -177,6 +177,7 @@ open_read(std::istream *source, bool owns_source, const std::string &password) { _read_overflow_buffer = new unsigned char[_read_block_size]; _in_read_overflow_buffer = 0; + _finished = false; thread_consider_yield(); } @@ -322,6 +323,57 @@ close_write() { } } +/** + * Implements seeking within the stream. EncryptStreamBuf only allows seeking + * back to the beginning of the stream. + */ +std::streampos EncryptStreamBuf:: +seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which) { + if (which != std::ios::in) { + // We can only do this with the input stream. + return -1; + } + + if (off != 0 || dir != std::ios::beg) { + // We only know how to reposition to the beginning. + return -1; + } + + size_t n = egptr() - gptr(); + gbump(n); + + if (_source->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { + int result = EVP_DecryptInit(_read_ctx, nullptr, nullptr, nullptr); + nassertr_always(result > 0, -1); + + _source->clear(); + _in_read_overflow_buffer = 0; + _finished = false; + + // Skip past the header. + int iv_length = EVP_CIPHER_CTX_iv_length(_read_ctx); + _source->ignore(6 + iv_length); + + // Ignore the magic bytes. + size_t magic_length = get_magic_length(); + char *buffer = (char *)alloca(magic_length); + if (read_chars(buffer, magic_length) == magic_length) { + return 0; + } + } + + return -1; +} + +/** + * Implements seeking within the stream. EncryptStreamBuf only allows seeking + * back to the beginning of the stream. + */ +std::streampos EncryptStreamBuf:: +seekpos(std::streampos pos, ios_openmode which) { + return seekoff(pos, std::ios::beg, which); +} + /** * Called by the system ostream implementation when its internal buffer is * filled, plus one character. @@ -423,7 +475,7 @@ read_chars(char *start, size_t length) { do { // Get more bytes from the stream. - if (_read_ctx == nullptr) { + if (_read_ctx == nullptr || _finished) { return 0; } @@ -439,8 +491,7 @@ read_chars(char *start, size_t length) { } else { result = EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read); - EVP_CIPHER_CTX_free(_read_ctx); - _read_ctx = nullptr; + _finished = true; } if (result <= 0) { diff --git a/dtool/src/prc/encryptStreamBuf.h b/dtool/src/prc/encryptStreamBuf.h index 7bc4db5199..2922aec939 100644 --- a/dtool/src/prc/encryptStreamBuf.h +++ b/dtool/src/prc/encryptStreamBuf.h @@ -44,6 +44,12 @@ public: INLINE void set_iteration_count(int iteration_count); INLINE int get_iteration_count() const; + INLINE void set_magic_length(size_t length); + INLINE size_t get_magic_length() const; + + virtual std::streampos seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which); + virtual std::streampos seekpos(std::streampos pos, ios_openmode which); + protected: virtual int overflow(int c); virtual int sync(); @@ -71,6 +77,9 @@ private: EVP_CIPHER_CTX *_write_ctx; size_t _write_block_size; + + size_t _magic_length = 0; + bool _finished = false; }; #include "encryptStreamBuf.I" diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index 4467132b79..f857940774 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -2068,10 +2068,7 @@ open_read_subfile(Subfile *subfile) { stream = wrapper; // Validate the password by confirming that the encryption header matches. - char this_header[_encrypt_header_size]; - stream->read(this_header, _encrypt_header_size); - if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size || - memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) { + if (!wrapper->read_magic(_encrypt_header, _encrypt_header_size)) { express_cat.error() << "Unable to decrypt subfile " << subfile->_name << ".\n"; delete stream; diff --git a/tests/prc/test_encrypt_stream.py b/tests/prc/test_encrypt_stream.py new file mode 100644 index 0000000000..d87f31a72a --- /dev/null +++ b/tests/prc/test_encrypt_stream.py @@ -0,0 +1,19 @@ +from panda3d import core + +import pytest + + +@pytest.mark.skipif(not hasattr(core, 'IDecryptStream'), reason="Requires OpenSSL") +def test_decrypt_stream(): + encrypted = b'[\x00\x10\x00d\x00\x07K\x08\x03\xabS\x13L\xab\x93\x1b\x15\xe4\xeel\x80u o\xd0\x80aY_]\x10\x8a\xb5\xff\x9d1\xc9\xd3\xac\x95\x04\xd8\xdf\x10\xa1' + decrypted = b'abcdefghijklmnopqrstuvwxyz' + + ss = core.StringStream(encrypted) + ds = core.IDecryptStream(ss, False, '0123456789') + + assert ds.read(len(decrypted)) == decrypted + assert ds.readall() == b'' + + # Allow seeking back to the beginning + ds.seekg(0) + assert ds.readall() == decrypted From ece56eb0a729e9af37a537ea4b6322a803326f9e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 16:21:23 +0200 Subject: [PATCH 64/77] Update .gitignore [skip ci] --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0b40b42f79..de3e59aef4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,12 @@ /targetroot/ /dstroot/ -# Core dumps +# Core dumps and traces core core.* vgcore.* *.core +*.trace # Editor files/directories *.save @@ -26,6 +27,7 @@ vgcore.* /+DESC /+MANIFEST /pkg-plist +/debug.ks # Produced installer/executables /*.exe @@ -36,6 +38,7 @@ vgcore.* /*.dmg /*.whl /*.txz +/*.apk # CMake /build/ From 73200e0912c04c92b18ac8266e5a8c4763f7e34a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 19:16:46 +0200 Subject: [PATCH 65/77] Add Max Voss to BACKERS.md [skip ci] --- BACKERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BACKERS.md b/BACKERS.md index b86afc0e71..8c6cd2e965 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D. I ![Benefactors](https://opencollective.com/panda3d/tiers/benefactor.svg?avatarHeight=48&width=600) * Sam Edwards +* Max Voss ## Backers From c1c74e2cd30e5b090e952bdc3fcd0bbee284ae3b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 19:22:26 +0200 Subject: [PATCH 66/77] mathutil: add some more assertion checks to PerlinNoise2 --- panda/src/mathutil/perlinNoise2.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/panda/src/mathutil/perlinNoise2.cxx b/panda/src/mathutil/perlinNoise2.cxx index ef187beb9c..1dc576e08a 100644 --- a/panda/src/mathutil/perlinNoise2.cxx +++ b/panda/src/mathutil/perlinNoise2.cxx @@ -19,6 +19,9 @@ */ double PerlinNoise2:: noise(const LVecBase2d &value) const { + // If this triggers, you passed in 0 for table_size. + nassertr(!_index.empty(), make_nan(0.0)); + // Convert the vector to our local coordinate space. LVecBase2d vec = _input_xform.xform_point(value); @@ -41,9 +44,13 @@ noise(const LVecBase2d &value) const { double v = fade(y); // Hash coordinates of the 4 square corners (A, B, A + 1, and B + 1) + nassertr(X >= 0 && X + 1 < _index.size(), make_nan(0.0)); int A = _index[X] + Y; int B = _index[X + 1] + Y; + nassertr(A >= 0 && A + 1 < _index.size(), make_nan(0.0)); + nassertr(B >= 0 && B + 1 < _index.size(), make_nan(0.0)); + // and add blended results from 4 corners of square. double result = lerp(v, lerp(u, grad(_index[A], x, y), From 750afbb1886796a7daea2974bdfb61ce6d037a4b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 20:34:19 +0200 Subject: [PATCH 67/77] device: Linux fixes for Trust GXT 24 and a few other cheap gamepads Fixes #576 --- panda/src/device/evdevInputDevice.cxx | 55 +++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index fc89b5a5a4..69d2979f7f 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -65,6 +65,12 @@ enum QuirkBits { // Axes on the right stick are swapped, using x for y and vice versa. QB_right_axes_swapped = 64, + + // Has no trigger axes. + QB_no_analog_triggers = 128, + + // Alternate button mapping. + QB_alt_button_mapping = 256, }; static const struct DeviceMapping { @@ -85,10 +91,14 @@ static const struct DeviceMapping { {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller}, // Jess Tech Colour Rumble Pad {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_right_axes_swapped}, - // SPEED Link SL-6535-SBK-01 - {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0}, + // Trust GXT 24 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping}, // 8bitdo N30 Pro Controller {0x2dc8, 0x9001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z}, + // Generic gamepad + {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping | QB_rstick_from_z | QB_right_axes_swapped}, + // Generic gamepad without sticks + {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping}, // 3Dconnexion Space Traveller 3D Mouse {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Pilot 3D Mouse @@ -497,8 +507,10 @@ init_device() { axis = InputDevice::Axis::right_x; } } else if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::left_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::left_trigger; + have_analog_triggers = true; + } } else if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::z; } else { @@ -527,8 +539,13 @@ init_device() { axis = InputDevice::Axis::right_y; } } else if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::right_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } else { + // Special weird case for Trust GXT 24 + axis = InputDevice::Axis::right_y; + } } else { axis = InputDevice::Axis::yaw; } @@ -548,8 +565,10 @@ init_device() { break; case ABS_GAS: if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::right_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } } else { axis = InputDevice::Axis::accelerator; } @@ -974,6 +993,26 @@ map_button(int code, DeviceClass device_class, int quirks) { // BTN_THUMB and BTN_THUMB2 detect touching the touchpads. return ButtonHandle::none(); + } else if (device_class == DeviceClass::gamepad && + (quirks & QB_alt_button_mapping) != 0) { + static const ButtonHandle mapping[] = { + GamepadButton::face_y(), + GamepadButton::face_b(), + GamepadButton::face_a(), + GamepadButton::face_x(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::ltrigger(), + GamepadButton::rtrigger(), + GamepadButton::back(), + GamepadButton::start(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if ((code & 0xf) < 12) { + return mapping[code & 0xf]; + } + } else if (device_class == DeviceClass::gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { From 2e9bd0f2415c44702d6300d8f84d554242ffcae3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 20:57:18 +0200 Subject: [PATCH 68/77] prc: fix compilation issue on MSVC --- dtool/src/prc/encryptStream.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dtool/src/prc/encryptStream.cxx b/dtool/src/prc/encryptStream.cxx index 380296dd8c..90e9e33858 100644 --- a/dtool/src/prc/encryptStream.cxx +++ b/dtool/src/prc/encryptStream.cxx @@ -22,7 +22,7 @@ */ bool IDecryptStream:: read_magic(const char *magic, size_t size) { - char this_magic[size]; + char *this_magic = (char *)alloca(size); read(this_magic, size); if (!fail() && gcount() == size && memcmp(this_magic, magic, size) == 0) { From c4a01ac564eabe1ebe95a7f561f4b52b2ebf9bd2 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 12 May 2019 19:53:27 +0200 Subject: [PATCH 69/77] pipeline: give Mutex and ReMutex more Pythonic semantics This allows using mutices in with-blocks and wraps up the functionality of acquire() and try_acquire() into a single acquire(blocking=True). Furthermore, the GIL is no longer released in cases of no contention. --- direct/src/stdpy/threading.py | 23 ------------ panda/src/pipeline/mutexDebug.I | 2 ++ panda/src/pipeline/mutexDirect.I | 2 ++ panda/src/pipeline/pmutex.h | 4 +++ panda/src/pipeline/pmutex_ext.I | 53 ++++++++++++++++++++++++++++ panda/src/pipeline/pmutex_ext.h | 41 +++++++++++++++++++++ panda/src/pipeline/reMutex.h | 4 +++ panda/src/pipeline/reMutexDirect.I | 4 +++ panda/src/pipeline/reMutex_ext.I | 53 ++++++++++++++++++++++++++++ panda/src/pipeline/reMutex_ext.h | 41 +++++++++++++++++++++ tests/pipeline/test_condition_var.py | 11 +++--- tests/pipeline/test_mutex.py | 26 ++++++++++++++ 12 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 panda/src/pipeline/pmutex_ext.I create mode 100644 panda/src/pipeline/pmutex_ext.h create mode 100644 panda/src/pipeline/reMutex_ext.I create mode 100644 panda/src/pipeline/reMutex_ext.h diff --git a/direct/src/stdpy/threading.py b/direct/src/stdpy/threading.py index 45409f9e18..8903a2fbc0 100644 --- a/direct/src/stdpy/threading.py +++ b/direct/src/stdpy/threading.py @@ -201,17 +201,6 @@ class Lock(core.Mutex): def __init__(self, name = "PythonLock"): core.Mutex.__init__(self, name) - def acquire(self, blocking = True): - if blocking: - core.Mutex.acquire(self) - return True - else: - return core.Mutex.tryAcquire(self) - - __enter__ = acquire - - def __exit__(self, t, v, tb): - self.release() class RLock(core.ReMutex): """ This class provides a wrapper around Panda's ReMutex object. @@ -221,18 +210,6 @@ class RLock(core.ReMutex): def __init__(self, name = "PythonRLock"): core.ReMutex.__init__(self, name) - def acquire(self, blocking = True): - if blocking: - core.ReMutex.acquire(self) - return True - else: - return core.ReMutex.tryAcquire(self) - - __enter__ = acquire - - def __exit__(self, t, v, tb): - self.release() - class Condition(core.ConditionVarFull): """ This class provides a wrapper around Panda's ConditionVarFull diff --git a/panda/src/pipeline/mutexDebug.I b/panda/src/pipeline/mutexDebug.I index ae3ce74967..3259f2aa67 100644 --- a/panda/src/pipeline/mutexDebug.I +++ b/panda/src/pipeline/mutexDebug.I @@ -70,6 +70,8 @@ acquire(Thread *current_thread) const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool MutexDebug:: try_acquire(Thread *current_thread) const { diff --git a/panda/src/pipeline/mutexDirect.I b/panda/src/pipeline/mutexDirect.I index 71a26543a7..3daf53a179 100644 --- a/panda/src/pipeline/mutexDirect.I +++ b/panda/src/pipeline/mutexDirect.I @@ -60,6 +60,8 @@ acquire() const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool MutexDirect:: try_acquire() const { diff --git a/panda/src/pipeline/pmutex.h b/panda/src/pipeline/pmutex.h index 2a47b7dbac..51b588a0c9 100644 --- a/panda/src/pipeline/pmutex.h +++ b/panda/src/pipeline/pmutex.h @@ -49,6 +49,10 @@ PUBLISHED: void operator = (const Mutex ©) = delete; + EXTENSION(bool acquire(bool blocking=true) const); + EXTENSION(bool __enter__()); + EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *)); + public: // This is a global mutex set aside for the purpose of protecting Notify // messages from being interleaved between threads. diff --git a/panda/src/pipeline/pmutex_ext.I b/panda/src/pipeline/pmutex_ext.I new file mode 100644 index 0000000000..5f3ba1fbe2 --- /dev/null +++ b/panda/src/pipeline/pmutex_ext.I @@ -0,0 +1,53 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +acquire(bool blocking) const { + if (_this->try_lock()) { + return true; + } + + if (!blocking) { + return false; + } + + // Release the GIL while we are waiting for the lock. +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + _this->lock(); + Py_BLOCK_THREADS +#else + _this->lock(); +#endif + return true; +} + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +__enter__() { + return acquire(true); +} + +/** + * Releases the mutex. + */ +INLINE void Extension:: +__exit__(PyObject *, PyObject *, PyObject *) { + _this->unlock(); +} diff --git a/panda/src/pipeline/pmutex_ext.h b/panda/src/pipeline/pmutex_ext.h new file mode 100644 index 0000000000..427d0c87f6 --- /dev/null +++ b/panda/src/pipeline/pmutex_ext.h @@ -0,0 +1,41 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +#ifndef PMUTEX_EXT_H +#define PMUTEX_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "pmutex.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for Mutex, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + INLINE bool acquire(bool blocking) const; + INLINE bool __enter__(); + INLINE void __exit__(PyObject *, PyObject *, PyObject *); +}; + +#include "pmutex_ext.I" + +#endif // HAVE_PYTHON + +#endif // PMUTEX_EXT_H diff --git a/panda/src/pipeline/reMutex.h b/panda/src/pipeline/reMutex.h index bdf9031304..bb87953949 100644 --- a/panda/src/pipeline/reMutex.h +++ b/panda/src/pipeline/reMutex.h @@ -42,6 +42,10 @@ PUBLISHED: ~ReMutex() = default; void operator = (const ReMutex ©) = delete; + + EXTENSION(bool acquire(bool blocking=true) const); + EXTENSION(bool __enter__()); + EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *)); }; #include "reMutex.I" diff --git a/panda/src/pipeline/reMutexDirect.I b/panda/src/pipeline/reMutexDirect.I index e7fa3a6fce..4e2fdb9abb 100644 --- a/panda/src/pipeline/reMutexDirect.I +++ b/panda/src/pipeline/reMutexDirect.I @@ -105,6 +105,8 @@ acquire(Thread *current_thread) const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool ReMutexDirect:: try_acquire() const { @@ -119,6 +121,8 @@ try_acquire() const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool ReMutexDirect:: try_acquire(Thread *current_thread) const { diff --git a/panda/src/pipeline/reMutex_ext.I b/panda/src/pipeline/reMutex_ext.I new file mode 100644 index 0000000000..8c28108f4c --- /dev/null +++ b/panda/src/pipeline/reMutex_ext.I @@ -0,0 +1,53 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +acquire(bool blocking) const { + if (_this->try_lock()) { + return true; + } + + if (!blocking) { + return false; + } + + // Release the GIL while we are waiting for the lock. +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + _this->lock(); + Py_BLOCK_THREADS +#else + _this->lock(); +#endif + return true; +} + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +__enter__() { + return acquire(true); +} + +/** + * Releases the mutex. + */ +INLINE void Extension:: +__exit__(PyObject *, PyObject *, PyObject *) { + _this->unlock(); +} diff --git a/panda/src/pipeline/reMutex_ext.h b/panda/src/pipeline/reMutex_ext.h new file mode 100644 index 0000000000..72eb50305d --- /dev/null +++ b/panda/src/pipeline/reMutex_ext.h @@ -0,0 +1,41 @@ +/** + * 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 remutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +#ifndef REMUTEX_EXT_H +#define REMUTEX_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "reMutex.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for ReMutex, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + INLINE bool acquire(bool blocking) const; + INLINE bool __enter__(); + INLINE void __exit__(PyObject *, PyObject *, PyObject *); +}; + +#include "reMutex_ext.I" + +#endif // HAVE_PYTHON + +#endif // REMUTEX_EXT_H diff --git a/tests/pipeline/test_condition_var.py b/tests/pipeline/test_condition_var.py index 38c7c4cf25..4cfc6c226a 100644 --- a/tests/pipeline/test_condition_var.py +++ b/tests/pipeline/test_condition_var.py @@ -26,13 +26,12 @@ def test_cvar_notify_locked(): m = Mutex() cv = ConditionVarFull(m) - m.acquire() - cv.notify() - m.release() + with m: + cv.notify() + + with m: + cv.notify_all() - m.acquire() - cv.notify_all() - m.release() del cv diff --git a/tests/pipeline/test_mutex.py b/tests/pipeline/test_mutex.py index 668d275b08..15fa435da7 100644 --- a/tests/pipeline/test_mutex.py +++ b/tests/pipeline/test_mutex.py @@ -2,6 +2,7 @@ from panda3d.core import Mutex, ReMutex from panda3d import core from random import random import pytest +import sys def test_mutex_acquire_release(): @@ -34,6 +35,19 @@ def test_mutex_try_acquire(): m.release() +def test_mutex_with(): + m = Mutex() + + rc = sys.getrefcount(m) + with m: + assert m.debug_is_locked() + + with m: + assert m.debug_is_locked() + + assert rc == sys.getrefcount(m) + + @pytest.mark.skipif(not core.Thread.is_threading_supported(), reason="Threading support disabled") def test_mutex_contention(): @@ -124,3 +138,15 @@ def test_remutex_try_acquire(): m.release() m.release() + +def test_remutex_with(): + m = ReMutex() + + rc = sys.getrefcount(m) + with m: + assert m.debug_is_locked() + with m: + assert m.debug_is_locked() + assert m.debug_is_locked() + + assert rc == sys.getrefcount(m) From e8fc76747a1821c70802eaf375acc6769a48d301 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 13:52:19 +0200 Subject: [PATCH 70/77] showbase: rename attachInputDevice gui=True arg to watch=True It describes more accurately what it does, which is attach the MouseWatcher to it. Though it was intended to help with GUI navigation, it can also be used for eg. polling button states. --- direct/src/showbase/ShowBase.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 5523d91a98..0dd8a05b3b 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1679,16 +1679,18 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) - def attachInputDevice(self, device, prefix=None, gui=False): + def attachInputDevice(self, device, prefix=None, watch=False): """ This function attaches an input device to the data graph, which will cause the device to be polled and generate events. If a prefix is given and not None, it is used to prefix events generated by this device, separated by a hyphen. - The gui argument can be set to True (as of Panda3D 1.10.3) to set up + The watch argument can be set to True (as of Panda3D 1.10.3) to set up the default MouseWatcher to receive inputs from this device, allowing - it to control user interfaces. + it to be polled via mouseWatcherNode and control user interfaces. + Setting this to True will also make it generate unprefixed events, + regardless of the specified prefix. If you call this, you should consider calling detachInputDevice when you are done with the device or when it is disconnected. @@ -1700,7 +1702,7 @@ class ShowBase(DirectObject.DirectObject): idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name)) # Setup the button thrower to generate events for the device. - if prefix is not None or not gui: + if prefix is not None or not watch: bt = idn.attachNewNode(ButtonThrower(device.name)) if prefix is not None: bt.node().setPrefix(prefix + '-') @@ -1709,7 +1711,7 @@ class ShowBase(DirectObject.DirectObject): assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix)) self.__inputDeviceNodes[device] = idn - if gui: + if watch: idn.node().addChild(self.mouseWatcherNode) def detachInputDevice(self, device): From f183d901cbeadcd1cf021c4edd66fe8d7e68535e Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 13:31:36 +0200 Subject: [PATCH 71/77] bullet: sync rigid body transform when node is reparented Reparenting a node will change its net transform, so it should cause a transform sync. Fixes #629 --- panda/src/bullet/bulletRigidBodyNode.cxx | 14 ++++++++++++++ panda/src/bullet/bulletRigidBodyNode.h | 1 + 2 files changed, 15 insertions(+) diff --git a/panda/src/bullet/bulletRigidBodyNode.cxx b/panda/src/bullet/bulletRigidBodyNode.cxx index b97f768d1b..acd7e96e79 100644 --- a/panda/src/bullet/bulletRigidBodyNode.cxx +++ b/panda/src/bullet/bulletRigidBodyNode.cxx @@ -359,6 +359,20 @@ do_transform_changed() { } } +/** + * + */ +void BulletRigidBodyNode:: +parents_changed() { + + if (_motion.sync_disabled()) return; + + if (get_num_parents() > 0) { + LightMutexHolder holder(BulletWorld::get_global_lock()); + do_transform_changed(); + } +} + /** * */ diff --git a/panda/src/bullet/bulletRigidBodyNode.h b/panda/src/bullet/bulletRigidBodyNode.h index d0e11da6cf..246dec3e6c 100644 --- a/panda/src/bullet/bulletRigidBodyNode.h +++ b/panda/src/bullet/bulletRigidBodyNode.h @@ -112,6 +112,7 @@ public: void do_sync_b2p(); protected: + virtual void parents_changed(); virtual void transform_changed(); private: From 291f3825f423ad01d347b85e7928f4f3fba05b7c Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 13:33:12 +0200 Subject: [PATCH 72/77] ffmpeg: fix rare "bad src image pointers" after seek Fixes #391 --- panda/src/ffmpeg/ffmpegVideoCursor.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panda/src/ffmpeg/ffmpegVideoCursor.cxx b/panda/src/ffmpeg/ffmpegVideoCursor.cxx index fa5789b6c4..23314561e9 100644 --- a/panda/src/ffmpeg/ffmpegVideoCursor.cxx +++ b/panda/src/ffmpeg/ffmpegVideoCursor.cxx @@ -755,7 +755,9 @@ do_poll() { PT(FfmpegBuffer) frame = do_alloc_frame(); nassertr(frame != nullptr, false); _lock.release(); - advance_to_frame(seek_frame); + if (seek_frame != _begin_frame) { + advance_to_frame(seek_frame); + } if (_frame_ready) { export_frame(frame); _lock.acquire(); From 7b77888e5ab091ffe814bb28bf7cea6b1eef40d7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 14:24:23 +0200 Subject: [PATCH 73/77] FilterManager: allow specifying custom fbprops in renderQuadInto Corollary to ea0210640cebe63db93333d13ead2e33ddaff188 (see #599) --- direct/src/filter/FilterManager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/direct/src/filter/FilterManager.py b/direct/src/filter/FilterManager.py index 5696ac9460..c20cbed139 100644 --- a/direct/src/filter/FilterManager.py +++ b/direct/src/filter/FilterManager.py @@ -239,7 +239,7 @@ class FilterManager(DirectObject): return quad - def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None): + def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None): """ Creates an offscreen buffer for an intermediate computation. Installs a quad into the buffer. Returns @@ -253,7 +253,10 @@ class FilterManager(DirectObject): depthbits = bool(depthtex != None) - buffer = self.createBuffer(name, winx, winy, texgroup, depthbits) + if fbprops is not None: + buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops) + else: + buffer = self.createBuffer(name, winx, winy, texgroup, depthbits) if (buffer == None): return None From 65491fdc0ff2b3836289f1f2e17d0e8d2eb0ca0c Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 14:25:40 +0200 Subject: [PATCH 74/77] doc: add release notes for 1.10.3 --- doc/ReleaseNotes | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/doc/ReleaseNotes b/doc/ReleaseNotes index 41d69d7f99..37156c7dab 100644 --- a/doc/ReleaseNotes +++ b/doc/ReleaseNotes @@ -1,3 +1,40 @@ +------------------------ RELEASE 1.10.3 ----------------------- + +This is another bugfix release that addresses a variety of issues +in 1.10.2 and further improves the stability. + +* Fix crash when unplugging certain devices on macOS +* Fix crash on macOS when using RIME input +* Fix logging issues/crashes in apps deployed with Python 2.7 +* Fix issues when starting in fullscreen on Linux/X11 +* Fix mapping of several gamepads including Trust GXT 24 +* Fix Linux crash when no input devices are present +* Unbreak support for matrix arrays in vertex data in OpenGL +* Allow creating multisample FBO in OpenGL with non-MS host window +* Support playing and looping compressed Ogg and WAV audio files +* Fix generation of CollisionBox for transformed geometry in .egg +* Fix Bullet rigid body transform not updating after reparenting +* Fix sporadic color scales with lighting and custom GLSL shader +* Prevent faulty shaders from shutting down GSG on some drivers +* Allow None as either argument to OdeJoint.attach() +* Fix BufferViewer when main window is not opened right away +* Properly detect extension of pz/gz compressed video/audio files +* Fix for invalid behavior of SparseArray methods to clear bits +* FilterManager now allows overriding framebuffer properties +* Fix detection of core-only OpenGL profile on some drivers +* Add gl-forward-compatible config var for OpenGL context creation +* Add paste-emit-keystrokes variable to disable Ctrl+V on Windows +* Fix in-place |= operator on Panda types (such as SparseArray) +* Fix rare FFmpeg "bad src image pointers" errors after seek +* Fix uses of types.InstanceType in some obscure direct functions +* Fix capsule-into-sphere collision test in degenerate case +* KeyboardButton.ascii_key now also accepts a str character +* Fix errors in various Tkinter DIRECT widgets +* Expose save_egg_file/save_egg_data functions in Python API +* Fix assertion error in BoundingBox.set_min_max +* Fix typo in CollisionTraverser.respect_prev_transform property +* Properly install Python bindings when building FreeBSD installer + ------------------------ RELEASE 1.10.2 ----------------------- This release fixes several more bugs, including a few regressions From adaf9ee4aa908cef4d2e99895132b716f3f21b2a Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 13 May 2019 14:31:19 +0200 Subject: [PATCH 75/77] readme: update links to point to 1.10.3 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7002275d13..b18bcd65b7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Installing Panda3D ================== The latest Panda3D SDK can be downloaded from -[this page](https://www.panda3d.org/download/sdk-1-10-2/). +[this page](https://www.panda3d.org/download/sdk-1-10-3/). If you are familiar with installing Python packages, you can use the following comand: @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win64.zip -https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win32.zip +https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win64.zip +https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win32.zip After acquiring these dependencies, you may simply build Panda3D from the command prompt using the following command. (Change `14.1` to `14` if you are @@ -135,7 +135,7 @@ macOS ----- On macOS, you will need to download a set of precompiled thirdparty packages in order to -compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-mac.tar.gz). +compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-mac.tar.gz). After placing the thirdparty directory inside the panda3d source directory, you may build Panda3D using a command like the following: From 83c10d1a0b17371dc126aa867c28102e54ee8371 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 29 May 2019 17:27:16 -0600 Subject: [PATCH 76/77] dtoolutil: Fix UB when musl's dlinfo(RTLD_DI_LINKMAP) fails --- dtool/src/dtoolutil/executionEnvironment.cxx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dtool/src/dtoolutil/executionEnvironment.cxx b/dtool/src/dtoolutil/executionEnvironment.cxx index 4152a8c356..9fce349268 100644 --- a/dtool/src/dtoolutil/executionEnvironment.cxx +++ b/dtool/src/dtoolutil/executionEnvironment.cxx @@ -610,16 +610,16 @@ read_args() { #else void *self = dlopen(NULL, RTLD_NOW | RTLD_NOLOAD); #endif - dlinfo(self, RTLD_DI_LINKMAP, &map); - - while (map != nullptr) { - const char *tail = strrchr(map->l_name, '/'); - const char *head = strchr(map->l_name, '/'); - if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 - || strcmp(tail, "/libp3dtool.so") == 0)) { - _dtool_name = head; + if (dlinfo(self, RTLD_DI_LINKMAP, &map)) { + while (map != nullptr) { + const char *tail = strrchr(map->l_name, '/'); + const char *head = strchr(map->l_name, '/'); + if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 + || strcmp(tail, "/libp3dtool.so") == 0)) { + _dtool_name = head; + } + map = map->l_next; } - map = map->l_next; } } #endif From 2f97b76b42311b5c90f14e5dd818f4ed47b8b3aa Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 2 Jun 2019 00:43:41 -0600 Subject: [PATCH 77/77] dtoolutil: Overhaul ExecutionEnvironment's dtool path hunting code The main change here is it uses an array of expected filenames, which optionally itself feeds off of a compiler definition, rather than hardcoding the expected filenames straight into the search code. The other change is this code is omitted when building statically. --- dtool/src/dtoolutil/executionEnvironment.cxx | 129 +++++++++++++------ 1 file changed, 91 insertions(+), 38 deletions(-) diff --git a/dtool/src/dtoolutil/executionEnvironment.cxx b/dtool/src/dtoolutil/executionEnvironment.cxx index 9fce349268..79d4a07e04 100644 --- a/dtool/src/dtoolutil/executionEnvironment.cxx +++ b/dtool/src/dtoolutil/executionEnvironment.cxx @@ -85,6 +85,44 @@ extern char **GLOBAL_ARGV; extern int GLOBAL_ARGC; #endif +// One of the responsibilities of ExecutionEnvironment is to determine the path +// to the binary file that contains itself (this is useful for making other +// components able to read files relative to Panda's installation directory). +// When built statically, this is easy - just use the main executable filename. +// When built shared, ExecutionEnvironment will introspect the memory map of +// the running process to look for dynamic library paths matching this list of +// predetermined filenames (ordered most likely to least likely). + +#ifndef LINK_ALL_STATIC +static const char *const libp3dtool_filenames[] = { +#if defined(LIBP3DTOOL_FILENAMES) + + // The build system is communicating the expected filename(s) for the + // libp3dtool dynamic library - no guesswork needed. + LIBP3DTOOL_FILENAMES + +#elif defined(WIN32_VC) + +#ifdef _DEBUG + "libp3dtool_d.dll", +#else + "libp3dtool.dll", +#endif + +#elif defined(__APPLE__) + + "libp3dtool." PANDA_ABI_VERSION_STR ".dylib", + "libp3dtool.dylib", + +#else + + "libp3dtool.so." PANDA_ABI_VERSION_STR, + "libp3dtool.so", + +#endif +}; +#endif /* !LINK_ALL_STATIC */ + // Linux with GNU libc does have global argvargc variables, but we can't // safely access them at stat init time--at least, not in libc5. (It does seem // to work with glibc2, however.) @@ -546,13 +584,14 @@ read_args() { // First, we need to fill in _dtool_name. This contains the full path to // the p3dtool library. -#ifdef WIN32_VC -#ifdef _DEBUG - HMODULE dllhandle = GetModuleHandle("libp3dtool_d.dll"); -#else - HMODULE dllhandle = GetModuleHandle("libp3dtool.dll"); -#endif - if (dllhandle != 0) { +#ifndef LINK_ALL_STATIC +#if defined(WIN32_VC) + for (const char *filename : libp3dtool_filenames) { + if (!_dtool_name.empty()) break; + + HMODULE dllhandle = GetModuleHandle(filename); + if (!dllhandle) continue; + static const DWORD buffer_size = 1024; wchar_t buffer[buffer_size]; DWORD size = GetModuleFileNameW(dllhandle, buffer, buffer_size); @@ -562,46 +601,44 @@ read_args() { _dtool_name = tmp; } } -#endif -#if defined(__APPLE__) +#elif defined(__APPLE__) // And on OSX we don't have procselfmaps, but some _dyld_* functions. - if (_dtool_name.empty()) { - uint32_t ic = _dyld_image_count(); - for (uint32_t i = 0; i < ic; ++i) { - const char *buffer = _dyld_get_image_name(i); - const char *tail = strrchr(buffer, '/'); - if (tail && (strcmp(tail, "/libp3dtool." PANDA_ABI_VERSION_STR ".dylib") == 0 - || strcmp(tail, "/libp3dtool.dylib") == 0)) { + uint32_t ic = _dyld_image_count(); + for (uint32_t i = 0; i < ic; ++i) { + if (!_dtool_name.empty()) break; + + const char *buffer = _dyld_get_image_name(i); + if (!buffer) continue; + const char *tail = strrchr(buffer, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { _dtool_name = buffer; + break; } } } -#endif -#if defined(RTLD_DI_ORIGIN) +#elif defined(RTLD_DI_ORIGIN) // When building with glibc/uClibc, we typically have access to RTLD_DI_ORIGIN in Unix-like operating systems. char origin[PATH_MAX + 1]; - if (_dtool_name.empty()) { - void *dtool_handle = dlopen("libp3dtool.so." PANDA_ABI_VERSION_STR, RTLD_NOW | RTLD_NOLOAD); + for (const char *filename : libp3dtool_filenames) { + if (!_dtool_name.empty()) break; + + void *dtool_handle = dlopen(filename, RTLD_NOW | RTLD_NOLOAD); if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) { _dtool_name = origin; - _dtool_name += "/libp3dtool.so." PANDA_ABI_VERSION_STR; - } else { - // Try the version of libp3dtool.so without ABI suffix. - dtool_handle = dlopen("libp3dtool.so", RTLD_NOW | RTLD_NOLOAD); - if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) { - _dtool_name = origin; - _dtool_name += "/libp3dtool.so"; - } + _dtool_name += '/'; + _dtool_name += filename; } } -#endif -#if !defined(RTLD_DI_ORIGIN) && defined(RTLD_DI_LINKMAP) +#elif defined(RTLD_DI_LINKMAP) // On platforms without RTLD_DI_ORIGIN, we can use dlinfo with RTLD_DI_LINKMAP to get the origin of a loaded library. if (_dtool_name.empty()) { struct link_map *map; @@ -612,12 +649,20 @@ read_args() { #endif if (dlinfo(self, RTLD_DI_LINKMAP, &map)) { while (map != nullptr) { - const char *tail = strrchr(map->l_name, '/'); + if (!_dtool_name.empty()) break; + const char *head = strchr(map->l_name, '/'); - if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 - || strcmp(tail, "/libp3dtool.so") == 0)) { - _dtool_name = head; + if (!head) continue; + const char *tail = strrchr(head, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { + _dtool_name = head; + break; + } } + map = map->l_next; } } @@ -634,19 +679,27 @@ read_args() { pifstream maps("/proc/self/maps"); #endif while (!maps.fail() && !maps.eof()) { + if (!_dtool_name.empty()) break; + char buffer[PATH_MAX]; buffer[0] = 0; maps.getline(buffer, PATH_MAX); - const char *tail = strrchr(buffer, '/'); const char *head = strchr(buffer, '/'); - if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 - || strcmp(tail, "/libp3dtool.so") == 0)) { - _dtool_name = head; + if (!head) continue; + const char *tail = strrchr(head, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { + _dtool_name = head; + break; + } } } maps.close(); } #endif +#endif /* !LINK_ALL_STATIC */ // Now, we need to fill in _binary_name. This contains the full path to the // currently running executable.