I have a qt application with an interactive viewport displaying a model.
The user can save a screenshot of the model to a file.
This is done by creating a new renderer, render window, camera and actors and then capturing the window pixels and storing them.
Problem is: after calling render() on the renderer connected to the offscreen window, the interactive window is completely black and sometimes the program terminates.
So I seem to have two independent pipelines that are somehow still influencing eachother.
I check SetSharedRenderWindow and it was null, just to be sure I explicitly set it to None on both windows.
The window has only one renderer which is the correct renderer.
The renderer has only one window which is the correct window.
I’m not yet able to produce a minimum reproduceable example. But the problem is reproducable on my pc.
I can only reproduce the issue when using a certain actor which is a mesh with a large amount (32008) of vertices that I create using the numpy2vtk functions. I can not reproduce the error without, so this may also be source of the problem (the changes in numpy 2.0 maybe?)
The offscreen renderer is executed in a separate python thread.
Hmm…how well does VTK like doing rendering from multiple threads? Are you reusing the pipeline and mappers perhaps? I wonder if OpenGL contexts get confused… (just musing out loud here)
I’ve reproduced the issue with an other (large) actor. So it is not mesh specific, it just needs to be large. In this case a 73728 faced closed sphere.
Tested with numpy > 2 as well, does not change anything.
We are rendering a pdf report with pictures showing different views of the model or models other than the one in the viewport.
The background thread is used to keep the gui responsive and give the user the opportunity to cancel the report production.
Producing the image without using a background thread works without issues.
It also works without issues (using a background thread) if I use actors with fewer vertices.
I have managed to boil it down to the following minimal example:
create a renderer in a window
start a thread
in the thread, create new renderer and render to an image
in the thread, make a list with a reference to the render window as well to the list itself. This can not be free-ed by the reference counter.
exit the thread
Execute the garbage-collector, this will clean up the list and the renderer window from the other thread. This causes the following error(s):
Python to reproduce:
import gc
from threading import Thread
import vtk
def create_interactive_window():
# Create a cube
cube = vtk.vtkCubeSource()
# Create a mapper
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(cube.GetOutputPort())
# Create an actor
actor = vtk.vtkActor()
actor.SetMapper(mapper)
# Create a renderer
renderer = vtk.vtkRenderer()
renderer.AddActor(actor)
renderer.SetBackground(0.1, 0.2, 0.4) # Background color
# Create a render window
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetSize(800, 600)
# Create a render window interactor
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Initialize and start the interaction
render_window.Render()
render_window_interactor.Initialize()
return render_window_interactor
def make_offscreen_renderer():
# Create a cube
cube = vtk.vtkCubeSource()
# Create a mapper
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(cube.GetOutputPort())
# Create an actor
actor = vtk.vtkActor()
actor.SetMapper(mapper)
# Create a renderer
renderer = vtk.vtkOpenGLRenderer()
renderer.AddActor(actor)
renderer.SetBackground(0.1, 0.2, 0.4) # Background color
renwin = vtk.vtkRenderWindow()
renwin.SetOffScreenRendering(True)
renwin.AddRenderer(renderer)
renwin.SetSize(800, 600)
camera = renderer.GetActiveCamera()
return renwin
if __name__ == '__main__':
#
gc.disable()
interactor = create_interactive_window()
#
#
def produce_offscreen_image():
renwin = make_offscreen_renderer()
renwin.Render()
# Use PIL to display the image (not required, just to check that it works)
#
# from PIL import Image
# from vtk.util.numpy_support import vtk_to_numpy
# import numpy as np
# nx, ny = renwin.GetSize()
#
# arr = vtk.vtkUnsignedCharArray()
# renwin.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
#
# narr = vtk_to_numpy(arr).T[:3].T.reshape([ny, nx, 3])
# narr = np.flip(narr, axis=0)
# pil_img = Image.fromarray(narr)
#
# pil_img.show()
whoopsie = [renwin] # <--- create a list with the renwin object
whoopsie.append(whoopsie) # <--- append the list to itself, to create a circular reference
# This means it will not be released because the reference count will never reach 0
new_thread = Thread(
target=lambda: produce_offscreen_image()
)
new_thread.start()
new_thread.join()
#
gc.collect(2) # <--- this crashes the program
interactor.Start()
Able to reproduce. You will need to clean up the render window on the thread which initialized the opengl context after you’re done rendering and saving a screenshot.
To fix it, call renwin.Finalize() at the end of your produce_offscreen_image() function.
FYI, here’s the human readable error message with a callstack, it appears FormatMessageW is making a garbled string.
COLLECTING #gc.collect(2)
( 1.067s) [main thread ]vtkWin32OpenGLRenderWin:259 ERR| vtkWin32OpenGLRenderWindow (000001F4B1F3DB70): wglMakeCurrent failed in MakeCurrent(), error: 2004(The requested transformation operation is not supported.)
( 1.390s) [main thread ]vtkWin32OpenGLRenderWin:260 WARN| vtkWin32OpenGLRenderWindow (000001F4B1F3DB70): at vtkWin32OpenGLRenderWindow::MakeCurrent
at vtkWin32OpenGLRenderWindow::Clean
at vtkWin32OpenGLRenderWindow::DestroyWindow
at vtkWin32OpenGLRenderWindow::~vtkWin32OpenGLRenderWindow
at vtkWin32OpenGLRenderWindow::~vtkWin32OpenGLRenderWindow
at vtkPythonUtil::RemoveObjectFromMap
at PyVTKObject_Delete
at Py_GetLocaleEncoding
at PyUnicode_DecodeUTF32Stateful
at PyModule_GetNameObject
at PyModule_GetNameObject
at PyImport_ImportModuleLevel
at PyArg_CheckPositional
at PyEval_EvalFrameDefault
at PyType_CalculateMetaclass
at PyEval_EvalCode
at PyArena_New
at PyArena_New
at PyRun_SimpleFileObject
at PyRun_SimpleFileObject
at PyRun_AnyFileObject
at Py_MakePendingCalls
at Py_MakePendingCalls
at Py_RunMain
at Py_RunMain
at vtkPythonInterpreter::PyMain
at vtkPythonInterpreter::PyMain
at vtkPythonInterpreter::PyMain
at BaseThreadInitThunk
at RtlUserThreadStart