Skip to content
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

Empty GLContext set by wxPython on windows prevents startup. #107

Open
shangjiaxuan opened this issue Mar 25, 2023 · 10 comments
Open

Empty GLContext set by wxPython on windows prevents startup. #107

shangjiaxuan opened this issue Mar 25, 2023 · 10 comments

Comments

@shangjiaxuan
Copy link

shangjiaxuan commented Mar 25, 2023

In file wxglslicecanvas.py the line

wxgl.GLCanvas          .__init__(self, parent, **attrs)

Sets the current wgl context to None (from OpenGL.WGL.GetCurrentContext()) on windows, and subsequent startup code will fail at any GL function (glGenTextures in current code) with INVALID_OPERATION.

A workaround would be calling self._setGLContext() immediately. But since while loading initial screen, the self.IsShownOnScreen() is always false, the workaround would require to commenting the following lines in __init__.py in fsleyes.gl's _setGLContext:

        if not (fwidgets.isalive(self) and self.IsShownOnScreen()):
            return False

With these edits, fsleyes starts the window on windows.

@shangjiaxuan
Copy link
Author

Line that changes current context to NULL on windows:

wxgl.GLCanvas .__init__(self, parent, **attrs)

Line that throws invalid operation error:

self.__texture = int(gl.glGenTextures(1))

@shangjiaxuan
Copy link
Author

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()

@shangjiaxuan
Copy link
Author

It seems reasonable to add the line to the end of WXGLCanvasTarget.__init__ to enforce this on all wxCanvas types:

self._setGLContext()

@pauldmccarthy
Copy link
Owner

Hi @shangjiaxuan, can you share a full stack trace of this error occurring?

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Mar 28, 2023

@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:

wxgl.GLCanvas .__init__(self, parent, **attrs)

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Mar 28, 2023

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?

@pauldmccarthy
Copy link
Owner

pauldmccarthy commented Mar 30, 2023

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:

  1. A "dummy" wx.Frame and wx.glcanvas.GLCanvas are created - these are used solely for the purposes of creating a GL context
  2. A wx.glcanavs.GLContext is created, using the dummy GLCanvas from step 1.
  3. The dummy GLCanvas is set as the rendering target (this happens here)
  4. The dummy 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
...

@shangjiaxuan
Copy link
Author

shangjiaxuan commented Mar 31, 2023

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

wxgl.GLCanvas .__init__(self, parent, **attrs)

instead of the first context creation. It's instantiating a specific subview, and context gets reset to NULL in the process.

@sercharpak
Copy link

Did you manage to solve this @shangjiaxuan ? If so could you share in a step by step process what changes were needed?

@shangjiaxuan
Copy link
Author

@sercharpak This is more of a workaround. You need to apply the following two changes;

  1. Restore the gl context:
    Empty GLContext set by wxPython on windows prevents startup. #107 (comment)
  2. Force the restore to happen when in loading screen:
    Empty GLContext set by wxPython on windows prevents startup. #107 (comment)

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

No branches or pull requests

3 participants