VTK 9 + PyQt + macOS = no rendering

Hi,

Using the latest VTK 9 wheel (from a couple of days ago) for macOS (tested on 10.15.4), I’m experiencing rendering issues. The plotter viewport is transparent with PyQt 5.12.3 and black with PyQt 5.9.2, and nothing is being displayed. I’ve seen this issue being reported also here (includes screenshot):
https://github.com/pyvista/pyvista/issues/562

Is there a workaround or any plans to address it?

Thanks!

@ben.boeckel I’ve confirmed that the problem reported here for QVTKRenderWindowInteractor.py occurs with the wheel, though it does not occur with my local build of VTK 9.0.0. What cmake options are used to build the wheel? My local build uses these:

BUILD_SHARED_LIBS:BOOL=ON
CMAKE_BUILD_TYPE:STRING=RelWithDebInfo
VTK_LEGACY_REMOVE:BOOL=OFF
VTK_PYTHON_VERSION:STRING=3
VTK_WRAP_PYTHON:BOOL=ON
CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9
CMAKE_OSX_SYSROOT:STRING=/path/to/MacOSX10.14.sdk

Minimal test (requires PySide2 or PyQt5): TestPyQtInteractor.py (2.3 KB)

@TJ_Corona built the macOS wheels. For the Linux wheels, they were stock shared Release builds with Python enabled inside the manylinux2014 containers (with the requisite packages installed for packages).

This could also be related to the way the wheels get built (VTK_WHEEL_BUILD=ON) inside the build system, but it only really configures some paths and such.

Thanks for the info. It actually turns out that I can reproduce the problem locally simply by using the “python” binary instead of using “vtkpython” (the latter is what I usually use for testing).

So the problem is not wheel-specific.

I tried out a few things to try to sort out why it works for “vtkpython” but fails for “python”.

First, I stripped down “vtkpython” so that all it did was call Py_Main(), just in case there was some magic happening in vtkPythonInterpreter. But even without the vtkPythonInterpreter stuff, PyQt + VTK worked fine.

Next, I downloaded the Python 3.7 source and built it myself. I targetted OS X 10.9, which is what the official binary package does. For this home-built python, PyQt + VTK still works fine.

Finally, I took just my home-built python executable, and copied it overtop of the official python3.7 executable. This also worked fine. So the issue is somehow with the shipped executable. Since the executable does nothing but call Py_Main(), the problem must have to do with the way that OS X is loading the executable.

My executable is built with the 10.14 SDK, their executable is built with the 10.9 SDK. Both target 10.9. And both were using their framework libraries.

Also, my executable links to the CoreFoundation framework, and theirs does not. Not sure what caused this difference, or what the implications are.

And, finally, their executable is signed and mine is not. Pretty sure this is unrelated to the rendering problem.

Hopefully someone else can pick things up from here and figure out what is going on…

Edit: One final test. The official Python binary package provides a Python.app with a Python.app/Contents/MacOS/Python executable. The PyQT+VTK test fails with this executable. When I replaced this Python with my home-built executable, PyQt+VTK works. This executable also links to CoreFoundation, and seems to differ from mine only in the SDK (10.9 vs 10.14) and the addition of a signature.

@panicbuttonvfx here is a workaround for you to try.

In your application, before you import the QVTKRenderWindowInteractor, add these two lines of code:

import vtkmodules.qt
vtkmodules.qt.QVTKRWIBase = "QGLWidget"

This causes the QVTKRenderWindowInteractor to be created as a subclass of QGLWidget, instead of being a direct subclass of QWidget.

If this fix works for your app on macOS, then if possible also give it a try on linux and Windows. If it works on all three, then I’ll probably make it the default for the next release.

@mwestphal Looks to be some QGLWidget stuff here.

Hi David, thanks (and everyone else) for looking into this issue. I’m using VTK through pyvista, and this workaround is already being implemented in their code for versions of VTK equal or higher than 8.2.0, and it doesn’t seem to work in this case:

# for display bugs due to older intel integrated GPUs
vtk_major_version = vtk.vtkVersion.GetVTKMajorVersion()
vtk_minor_version = vtk.vtkVersion.GetVTKMinorVersion()
if vtk_major_version == 8 and vtk_minor_version < 2:
    import vtk.qt
    vtk.qt.QVTKRWIBase = 'QGLWidget'
else:
    import vtkmodules.qt
    vtkmodules.qt.QVTKRWIBase = 'QGLWidget'

In any case, I’ve tried the workaround with the script you posted earlier (TestPyQtInteractor.py) and didn’t work either. I’ve tried this in both a MacBook Pro (Intel HD Graphics 530 + AMD Radeon Pro 460) and a MacPro 2020 (2x AMD Radeon Pro Vega II) and no luck

Hmm. Any chance that you can test the “build-your-own-python-executable” fix that I mentioned first? That works for me in all configurations: PySide2 or PyQt5, QGLWidget or QWidget. I realize that it’s not a deployable solution, I’d just be comforted by evidence that it isn’t specific to my system.

I’ve just tested it with Python 3.7.5 built from source and PySide2 5.14.2.1 (installed from pip), and it worked. I couldn’t try it with PyQt in this environment, and the app I want to update to VTK 9 uses PyQt and using PySide would mean some refactoring, so there’s that gap. I’m not sure if this tells you if the issue comes from Python or potentially from PyQt

Wait, when you talk about moving from PyQt to PySide, do you mean that it works with PySide and the original python? Right now I’m confused about why you’re considering moving your app from one to the other. For me, the “build-your-own-python” solution worked equally well for PyQt5 and PySide2.

From what I’ve seen, the problem is most likely related to the SDK used to build the Python executable. Unfortunately that doesn’t suggest anything that can be done in VTK to fix the issue. I mean, there probably is a VTK-related fix (after all, this problem didn’t occur with VTK 8.2), but there aren’t any clues as to what that fix would be.

I’ve used PySide2 with a built-from-source Python, not original, because the way the environment I’ve tried that in is setup, I don’t have access to PyQt5. The app I’m trying to update from VTK 8.2.0 to VTK 9.0.0 uses PyQt5 (it relies on a couple of PyQt5 specific things that work differently in PySide2, so it would need some refactoring), and I couldn’t try it with PySide2, to see if that would have made any difference.

So the gap is that I don’t know if trying your example script with PyQt5 (and built-from-source python) would have worked or failed, and I don’t know if trying my app with PySide2 (using Python installed from Conda) instead of PyQt5 would have worked or also failed.

Thanks for the explanation. I did some more prodding, and discovered that the problem was that the NSView (created by Qt) wasn’t connecting to the NSOpenGLContext (created by VTK). The [context setView:view]call in vtkCocoaRenderWindow was not doing anything. Basically, it seems that the Qt widget must be in a suitable state before the connection is made.

Things appear to work as long as the VTK Initialize() is moved to after the Qt show():

    # complete the initialization
    window.show()
    widget.Initialize()

    # start event processing
    widget.Start()
    app.exec_()

Example: TestPyQtInteractor.py (2.4 KB)

This might also allow pyvista to use QWidget instead of QGLWidget.

Edit: actually, since Start() calls Initialize() internally, the simplest approach is probably to not call Initialize() at all. Just ensure that Start() isn’t called until the very end:

    # call "show()" somewhere up here

    # start event processing
    widget.Start()
    app.exec_()

David, I wonder if this could be related to the changes we made here?

https://gitlab.kitware.com/vtk/vtk/-/merge_requests/6653/diffs?commit_id=363b37263a1438ec7322d5635e15b486ff2fcb43

Sean

Good catch, Sean. The problem can be fixed by putting back this additional setView that was removed from Start():

     NSOpenGLContext* context = (NSOpenGLContext*)this->GetContextId();
+    [context setView:(NSView*)this->GetWindowId()];
     [context update];

So that’s easy enough to do.

Thank you all for looking into this issue. Glad to see you’ve found an easy solution for it. Do you know when the fix will be available?

I’ve already put in a merge request, but if you’re wondering when new wheels will be available, my guess is two to four weeks. Don’t forget that moving the Start() call in in the Python code also seems to fix the issue.

Thanks for the update David. I’ve tried calling Start() for the instance of QVTKRenderWindowInteractor I’m creating through pyvista, but it didn’t work. It might need to be done directly in the library, so I’ll dig a bit to see if that would work.

The trick is to make sure that Initialize() and Start() are never called until after show() is called.