Open shangjiaxuan opened 1 year ago
Line that changes current context to NULL
on windows:
https://github.com/pauldmccarthy/fsleyes/blob/9a01ff62c6f1e6ad746092dc5045baefc6760e12/fsleyes/gl/wxglslicecanvas.py#L44
Line that throws invalid operation error: https://github.com/pauldmccarthy/fsleyes/blob/9a01ff62c6f1e6ad746092dc5045baefc6760e12/fsleyes/gl/textures/texture.py#L86
self._setGLContext()
needs to be after the context is retrieved in CanvasTarget
contruction, thus:
wxgl.GLCanvas .__init__(self, parent, **attrs)
fslgl.WXGLCanvasTarget .__init__(self)
self._setGLContext()
It seems reasonable to add the line to the end of WXGLCanvasTarget.__init__
to enforce this on all wxCanvas types:
self._setGLContext()
Hi @shangjiaxuan, can you share a full stack trace of this error occurring?
@pauldmccarthy of course. But I doubt the delayed error handling will give anything usefull.
python ./Lib/site-packages/fsleyes/main.py
gives:
WARNING logs.py 69: __call__ - Failure on glGenTextures: Traceback (most recent call last):
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\latebind.py", line 43, in __call__
return self._finalCall( *args, **named )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not callable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\logs.py", line 67, in __call__
return function( *args, **named )
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\error.py", line 230, in glCheckError
raise self._errorClass(
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glGenTextures,
cArguments = (1, array([0], dtype=uint32))
)
WARNING frame.py 1458: __restoreState - Previous layout could not be restored - falling back to default layout.
WARNING logs.py 69: __call__ - Failure on glGenTextures: Traceback (most recent call last):
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\logs.py", line 67, in __call__
return function( *args, **named )
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\error.py", line 230, in glCheckError
raise self._errorClass(
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glGenTextures,
cArguments = (1, array([0], dtype=uint32))
)
WARNING __init__.py 731: create - GLContext callback function raised GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glGenTextures,
pyArgs = (
1,
<object object at 0x0000013528666EB0>,
),
cArgs = (1, array([0], dtype=uint32)),
cArguments = (1, array([0], dtype=uint32))
)
Traceback (most recent call last):
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\__init__.py", line 728, in create
ready()
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\main.py", line 583, in realCallback
callback()
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\main.py", line 370, in buildGui
frame = makeFrame(namespace[0],
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\main.py", line 795, in makeFrame
frame = fsleyesframe.FSLeyesFrame(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\frame.py", line 311, in __init__
self.__restoreState(restore)
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\frame.py", line 1464, in __restoreState
layouts.loadLayout(self, 'default')
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\layouts.py", line 104, in loadLayout
applyLayout(frame, name, layout, **kwargs)
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\layouts.py", line 143, in applyLayout
frame.addViewPanel(vp, defaultLayout=False)
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\frame.py", line 511, in addViewPanel
panel = panelCls(self.__mainPanel, self.__overlayList, childDC, self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\views\orthopanel.py", line 219, in __init__
self.__labelMgr = ortholabels.OrthoLabels(
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\ortholabels.py", line 79, in __init__
cannots[side] = annot.text('', 0, 0, hold=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\annotations.py", line 228, in text
obj = TextAnnotation(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\annotations.py", line 1374, in __init__
self.__text = gltext.Text()
^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\text.py", line 117, in __init__
self.__texture = textures.Texture2D(
^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\textures\texture2d.py", line 192, in __init__
texture.Texture.__init__(self, name, 2, nvals, **kwargs)
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\textures\texture.py", line 556, in __init__
TextureBase .__init__(self, name, ndims, nvals)
File "C:\Users\shang\fsleyes\Lib\site-packages\fsleyes\gl\textures\texture.py", line 86, in __init__
self.__texture = int(gl.glGenTextures(1))
^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\latebind.py", line 43, in __call__
return self._finalCall( *args, **named )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\wrapper.py", line 678, in wrapperCall
raise err
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\wrapper.py", line 671, in wrapperCall
result = wrappedOperation( *cArguments )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\logs.py", line 67, in __call__
return function( *args, **named )
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\shang\fsleyes\Lib\site-packages\OpenGL\error.py", line 230, in glCheckError
raise self._errorClass(
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glGenTextures,
pyArgs = (
1,
<object object at 0x0000013528666EB0>,
),
cArgs = (1, array([0], dtype=uint32)),
cArguments = (1, array([0], dtype=uint32))
)
Then the floating load window freezes at Creating FSLeyes interface...
I suggest adding log.error("{}".format(OpenGL.WGL.GetCurrentContext()))
and log.error("{}".format(OpenGL.GLX.GetCurrentContext()))
(linux x11, EGL for wayland? and CGL for mac?) before and after this line (and anywhere to see if a context is current) to see my point:
https://github.com/pauldmccarthy/fsleyes/blob/9a01ff62c6f1e6ad746092dc5045baefc6760e12/fsleyes/gl/wxglslicecanvas.py#L44
Typically I would assume GLCanvas to be HDC
(Surface
in egl/gles
terms, or a Swapchain
in modern graphics (dxgi
directx (d3d10+
) and vulkan
) terms, Drawable
in x11 terms). This maps to the Target
concept in fsleyes
. The OpenGL context is HGLRC
or ID3D11Device
+ID3D11DeviceContext
or VkDevice
+VkQueue
equavalent, and is separate from window itself on most platforms.
Since wxGLCanvas
inherits from wxWindow
, the equavalent will be HWND
+HDC
on windows OpenGL. The event system is coupled into image output. This reflects MacOS CGL limitation of tying a context to a window. However, there are more in discrepancy than that:
For event system (Window): Windows window can only process events on the creating thread, X11 window can do those on any thread, while Cocoa window can only process events from main thread GCD.
For image output (SwapChain) relationship with event system (Window): On windows and x11 this seems to seperable, on MacOS this seems to be done in Cocoa Views, which I don't quite understand.
For image output (SwapChain) relationship with context (Device): WGL and GLX can use the same context for all image output, CGL context is always bound to one specific image output.
These discrepancies seems to have made its way to upper wx functions?
Hi @shangjiaxuan, apologies for the delay (busy week). Can I also ask how you have installed FSLeyes?
I think you are probably correct in that the behaviour differs across platforms. I'm not sure if this should be considered a bug in wxPython/wxWidgets, as OpenGL context creation/management is an inherently platform-specific process. And I certainly have no objection to adding a work-around to the FSLeyes codebase, although I don't have access to a Windows machine, and my naive attempts to install FSLeyes on an Amazon EC2 were unsuccessful due to the absence of a modern GL driver.
The process that FSLeyes uses to initialise the GL context is as follows:
wx.Frame
and wx.glcanvas.GLCanvas
are created - these are used solely for the purposes of creating a GL contextwx.glcanavs.GLContext
is created, using the dummy GLCanvas
from step 1.GLCanvas
is set as the rendering target (this happens here)wx.Frame
is hidden, but kept alive, for the duration of execution.On my personal machine (Ubuntu 22.04, EGL), OpenGL.EGL.eglGetCurrentContext.address
returns NULL before setting the rendering target in step 3, and then non-NULL immediately afterwards. Would you be able to perform the same check on your own system?
def getctx():
import OpenGL.EGL as egl
try:
return str(egl.eglGetCurrentContext().address)
except ValueError:
return 'NULL'
...
print(getctx()) # prints NULL
self.__context.SetCurrent(self.__canvas) # line 967, linked above in step 3
print(getctx()) # prints non-NULL
...
I installed fsleyes using pip install fsleyes
. On windows new wxPython
installation does not resolve two dependencies and need to be manually installed: attrdict3
requests
.
The context creation as mentioned was successful (first call to getContext succeeds and sets the context). It's when later after overlay info are loaded, when the initializing a specific overlay (orthopanel
in this case for new setup), that the error happens. Context creation is called only once.
I added lots of logging in the code and saw that context creation was successful. It's after creating the "view subwindow" that the context gets "reset" to NULL
. This NULL
persists until the error is thrown. This is the reason I referenced the line
instead of the first context creation. It's instantiating a specific subview, and context gets reset to NULL in the process.
Did you manage to solve this @shangjiaxuan ? If so could you share in a step by step process what changes were needed?
@sercharpak This is more of a workaround. You need to apply the following two changes;
In file
wxglslicecanvas.py
the lineSets the current wgl context to
None
(fromOpenGL.WGL.GetCurrentContext()
) on windows, and subsequent startup code will fail at any GL function (glGenTextures
in current code) withINVALID_OPERATION
.A workaround would be calling
self._setGLContext()
immediately. But since while loading initial screen, theself.IsShownOnScreen()
is always false, the workaround would require to commenting the following lines in__init__.py
infsleyes.gl
's_setGLContext
:With these edits, fsleyes starts the window on windows.