Skip to content

WxRenderWidget cannot be used on its own (segfault) #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
tlambert03 opened this issue May 20, 2025 · 7 comments · May be fixed by #92
Open

WxRenderWidget cannot be used on its own (segfault) #91

tlambert03 opened this issue May 20, 2025 · 7 comments · May be fixed by #92

Comments

@tlambert03
Copy link
Contributor

tlambert03 commented May 20, 2025

running the following example causes a segfault (rendercanvas v2.1.2, wxpython 4.2.3, macos, python 3.12)

import rendercanvas.wx
import wx

app = wx.App()
widget = rendercanvas.wx.WxRenderWidget()

while WxRenderCanvas() works just fine...

the fault seems to be happening in a size-related method:

➜ lldb python -- x.py
(lldb) target create "python"
Current executable set to '/Users/talley/dev/self/ndv/.venv/bin/python' (arm64).
(lldb) settings set -- target.run-args  "x.py"
(lldb) run
Process 8261 launched: '/Users/talley/dev/self/ndv/.venv/bin/python' (arm64)
Process 8261 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00000001032dd158 libwx_osx_cocoau_core-3.2.0.4.0.dylib`wxWindow::DoGetPosition(int*, int*) const + 52
libwx_osx_cocoau_core-3.2.0.4.0.dylib`wxWindow::DoGetPosition:
->  0x1032dd158 <+52>: ldr    x8, [x0]
    0x1032dd15c <+56>: ldr    x8, [x8, #0x98]
    0x1032dd160 <+60>: add    x1, sp, #0xc
    0x1032dd164 <+64>: add    x2, sp, #0x8
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x00000001032dd158 libwx_osx_cocoau_core-3.2.0.4.0.dylib`wxWindow::DoGetPosition(int*, int*) const + 52
    frame #1: 0x0000000102ae55a8 _core.cpython-312-darwin.so`sipwxWindow::DoGetPosition(int*, int*) const + 156
    frame #2: 0x00000001032de498 libwx_osx_cocoau_core-3.2.0.4.0.dylib`wxWindow::DoSetSize(int, int, int, int, int) + 72
    frame #3: 0x0000000102ae5390 _core.cpython-312-darwin.so`sipwxWindow::DoSetSize(int, int, int, int, int) + 184
    frame #4: 0x0000000102af8828 _core.cpython-312-darwin.so`meth_wxWindow_SetSize(_object*, _object*, _object*) + 660
    frame #5: 0x0000000101631154 libpython3.12.dylib`cfunction_call + 72
    frame #6: 0x00000001014693b0 libpython3.12.dylib`_PyEval_EvalFrameDefault + 164064
    frame #7: 0x000000010165eb70 libpython3.12.dylib`method_vectorcall.llvm.13786586648554541543 + 172
    frame #8: 0x000000010146cd70 libpython3.12.dylib`_PyEval_EvalFrameDefault + 178848

and I tracked it down to this line in _rc_set_logical_size (which is triggered by _final_canvas_init)

self.SetSize(width, height)

@tlambert03
Copy link
Contributor Author

a follow up question (not sure if it's a bug or my misunderstanding of how WxRenderWidget() should be used)... but if I comment out that SetSize line above, I run into another issue, when trying to create a pygfx renderer targeting the widget:

import pygfx.renderers
import rendercanvas.wx
import wx

app = wx.App()
canvas = rendercanvas.wx.WxRenderWidget()
renderer = pygfx.renderers.WgpuRenderer(canvas)  # this line blocks and prevents further setup 

it gets caught in a loop here, which was triggered by WxRenderWidget._rc_get_present_methods:

while event_loop.Pending():
event_loop.Dispatch()

@almarklein
Copy link
Member

From what I understand, a toplevel widget should inherit from wx.Frame, and subwidgets from wx.Window. So the WxRenderWidget is intended to be embedded in a larger GUI, while the rendercanvas.wx.RenderCanvas is intended as a toplevel widget.

@tlambert03
Copy link
Contributor Author

tlambert03 commented May 21, 2025

Yes, I was going to be placing it within the context of a larger widget. But it still doesn’t work. Even if I pass in a parent frame it doesn’t work

Can you show me a simple example of embedding that doesn’t segfault or freeze?

@almarklein
Copy link
Member

Trying your original sample on Linux, I get not a segfault but it indeed errors at SetSize:

  File "/home/almar/dev/py/rendercanvas/rendercanvas/base.py", line 523, in set_logical_size
    self._rc_set_logical_size(width, height)
  File "/home/almar/dev/py/rendercanvas/rendercanvas/wx.py", line 359, in _rc_set_logical_size
    self.SetSize(width, height)
wx._core.wxAssertionError: C++ assertion ""m_widget"" failed at ./src/gtk/window.cpp(4057) in DoSetSize(): invalid window

When trying to come up with a minimal example, I indeed run into errors and segfaults rather easily. The order of operations matters.

Simplest form:

import rendercanvas.wx
import wx

app = wx.App()

frame = wx.Frame(None)

canvas = rendercanvas.wx.WxRenderWidget(parent=frame)

frame.SetSize(640, 480)
frame.Show()

app.MainLoop()

Slightly more advanced:

import rendercanvas.wx
import wx
import numpy as np


app = wx.App()

frame = wx.Frame(None)

canvas = rendercanvas.wx.WxRenderWidget(parent=frame)

frame.SetSize(640, 480)
frame.Show()

context = canvas.get_context("bitmap")
bitmap= np.full((100, 100, 4), 255, np.uint8)
bitmap[:,:,0] = 0
context.set_bitmap(bitmap)

app.MainLoop()

With an animation:

import rendercanvas.wx
import wx
import numpy as np


app = wx.App()

frame = wx.Frame(None)

canvas = rendercanvas.wx.WxRenderWidget(parent=frame, update_mode='continuous')

frame.SetSize(640, 480)
frame.Show()

context = canvas.get_context("bitmap")

@canvas.request_draw
def animate():
    w, h = canvas.get_logical_size()
    shape = int(h) // 4, int(w) // 4

    bitmap = np.random.uniform(0, 255, shape).astype(np.uint8)
    context.set_bitmap(bitmap)

app.MainLoop()

And then there is https://github.com/pygfx/rendercanvas/blob/main/examples/wx_app.py

@tlambert03
Copy link
Contributor Author

ok, fair enough! sorry I missed that example. was trying something too simple I guess.

do you think it would be worth guarding

self.SetSize(width, height)

behind if parent is not None?

@almarklein
Copy link
Member

See #92

I also found that if you use wx.Frame() instead of wx.Frame(None) in the snippets above, it segfaults 🤷 but we cannot detect that case.

@almarklein almarklein linked a pull request May 22, 2025 that will close this issue
@tlambert03
Copy link
Contributor Author

tlambert03 commented May 22, 2025

I commented at #92. The warning is fine, but I think it slightly misses the point that this is a little bit of a bug/limitation in rendercanvas. There shouldn’t be anything conceptually wrong with creating a widget without a parent and then later putting it into another widget before the event loop starts. It’s still not using it as a top level widget and I would have expected it to be a supported pattern

@almarklein almarklein reopened this May 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants