pygfx / rendercanvas

One canvas API, multiple backends
https://rendercanvas.readthedocs.io
BSD 2-Clause "Simplified" License
8 stars 0 forks source link

WindowManager: Linux Wayland support #36

Open almarklein opened 4 years ago

almarklein commented 4 years ago

Updated 17-09-2024

Current status

Up to 17-09-2024:

Introduction

Since Ubuntu 21.04, Wayland is the default display server. This means that this issue potentially affects a lot of users.

The XDG_SESSION_TYPE env variable is either x11 or wayland. This variable is used by many applications to select the window system. What's important for us is that glfw and qt (and wx?) use this variable too.

Forntunately, there is XWayland, a compatibility layer that allows applications to "talk X", but still run on Wayland. XWayland is installed by default on Ubuntu too.

This means we can tell glfw and qt to just use X, even when on Wayland. There are env vars for that.

How does it affect us exactly?

To obtain a surface id for the canvas to render to, we call wgpuInstanceCreateSurface(), the descriptor argument for that function is platform specific; there is a different struct for Windows, Metal, X11, Wayland, and Xcb.

On Windows and MacOS, that struct can be composed with just the "window id". Glfw, Qt and Wx have a metthod to obtain it. So Windows and MacOS are relatievely easy.

On Linux, apart from having to deal with multiple window platforms, we also need an additional value: the display id. This is basically a pointer to a display context: the thing an app creates to start doing things with x11/Wayland. A bit similar to a device of wgpu. This is where the hard part is.

Support for glfw

GLFW has improved support over the past years/months, but seems not quite 100% yet. Pyglfw on Linux includes one lib for x11 and one for Wayland, and selects one based on XDG_SESSION_TYPE and a few other variables.

The latest glfw (3.4, released 23-02-2024) ought to have better support for Wayland. And includes that support in a single binary lib. Unfortunately, there are some build problems, so pyglfw cannot ship with these binaries yet. It ships glfw 3.3.9 instead.

When I apt install libglfw3 it installs version 3.3.6 (on Ubunty 22.04). I don't feel like compiling glfw from source right now. So I have not tested the new glfw 3.4.

This snippet can be used to create a glfw window. Without the PYGLFW_LIBRARY_VARIANT this produces an unresponsive window without decorations on Wayland (with glfw 3.3.9).

import os

# os.environ["PYGLFW_LIBRARY_VARIANT"] = "x11"  # force using XWayland

import glfw

glfw.init()

glfw.window_hint(glfw.CLIENT_API, glfw.NO_API)
glfw.window_hint(glfw.RESIZABLE, True)

w = glfw.create_window(800, 800, "Test!", None, None)

while True:
    glfw.poll_events()

Also see https://github.com/gfx-rs/wgpu/issues/4775 and https://github.com/gfx-rs/wgpu-native/issues/377.

The solution for now (March 2024) is to force glfw to use X11 (i.e. XWayland on Wayland).

Support for Qt

The problem with Qt is that we cannot obtain the display id that Qt uses internally. There is QGuiApplication.nativeInterface but ... it's not implemented in PySide or PyQt. See e.g. https://wiki.qt.io/Qt_for_Python_Missing_Bindings. Though maybe its available soon?

Another thing I tried was to mage qt's WgpuCanvas a QWindow instead of a QWidget. This class has a surfaceHandle() method ... except it raises an exception saying the method is private.

Instead of using the actual display id that Qt uses, we can also create our own "display object" and use that. That works fine for X11. Unfortunately, this does not work for Wayland.

Then there is QT_QPA_PLATFORM, which can be set to (amongst other things "wayland-egl" and "xcb".

The solution for now (March 2024) is to set QT_QPA_PLATFORM to xcb to tell Qt to use X11 (i.e. XWayland on Wayland).

Support for wx

Can force to use x11 by setting GDK_BACKEND to "x11". But I haven't tested because cannot install wxPython with apt or pip.

almarklein commented 2 years ago

wgpu-native just got support: https://github.com/gfx-rs/wgpu-native/pull/160/ but apparently GLFW's support for Wayland is still experimental and pyGLFW may not support it yet.

almarklein commented 2 years ago

It looks like all the components are there to connect things up (with glfw), but when I run it, the system crashes and I'm back in my login screen 😆 I've spend some time checking and trying things, but no luck.

For Qt, this seems like a good staring point: https://doc.qt.io/qt-5/wayland-and-qt.html I have not looked at that myself yet.

Korijn commented 1 year ago

Current status: it's not known if the latest versions of GLFW and wgpu-py together support wayland or not. Needs verification before we can close.

hmaarrfk commented 1 year ago

I'm not too sure about the packages on pypi, but I've compiled qt and wayland for conda-forge: https://github.com/conda-forge/qt-wayland-feedstock

https://github.com/conda-forge/qt-main-feedstock/issues/120#issuecomment-1546916602

let me know if that helps.

If you feel that an other package is missing wayland support, I've found that:

  1. Upstream typically supports it in their development environmnet.
  2. Packaging strategies for PyPi somewhat lag support for fancy features due to packaging difficulties.

2 has been largely alleviated recently with the introduction of things like manylinux_20YY.

hmaarrfk commented 8 months ago

Qt did not work on Wayland.

can you expand on this? I was trying this a while back too, it seems that Python for Qt (PyQt6 and PySide6) likely doesn't expose the API required to make this work. My next attempt was to dig deeper into the C++ API to see if there was a small patch I could make to pyside2 to expose the required functions.

I'm not sure if you already tried this.

hmaarrfk commented 8 months ago

glfw did, kinda,[...] undecorated window.

regarding undecorated windows, it seems that this is a "choice" by Wayland, they are pushing the decorations on the client side, many frameworks now have a "switch" that you have to toggle if you want the "default" decorations.

I the decison to make the the client's (the application we are writing in our python+wgpu context) choice was one that came about because Chrome + Firefox were starting to try to make more use of the space typically reserved for "decorations".

almarklein commented 8 months ago

Qt did not work on Wayland.

can you expand on this?

Basically, we need the display handle (pointer to display object), and although Qt has API to get it, this is not exposed in PySide nor PyQt5. In X we deal with this by creating our own display object. Unfortunately, Wayland does not accept this trick (and the app segfaults). IIRC window ids on X are pretty big numbers, while on Wayland they're small ints. Which makes me think X uses globally unique window ids, where in Wayland they are namespaced to the display object.

I have not thought about submitting a patch to PySide / PyQt yet.

regarding undecorated windows, it seems that this is a "choice" by Wayland, t

Yeah, and I think glfw now does the decoration, but from what I read, I think it does this in glfw 3.4, which is not yet available via pyglfw.

hmaarrfk commented 8 months ago

I have not thought about submitting a patch to PySide / PyQt yet.

Do you recall the function we need to call from Qt's C++ API? If so, it would make it easier to open an issue on the PySide Bugtracker. They don't seem to have an active tracking issue: https://bugreports.qt.io/browse/PYSIDE-2190?jql=project+%3D+PYSIDE+AND+text+%7E+wayland

glfw 3.4

I tried to just install pyglfw from conda-forge, and for some reason I got:

$ mamba list glfw
# packages in environment at /home/mark/miniforge3/envs/dev:
#
# Name                    Version                   Build  Channel
glfw                      3.4                  hd590300_0    conda-forge
pyglfw                    2.7.0           py310hff52083_0    conda-forge
$ python triangle_glfw.py 
Detected skylake derivative running on mesa i915. Clears to srgb textures will use manual shader clears.
/home/mark/miniforge3/envs/dev/lib/python3.10/site-packages/glfw/__init__.py:914: GLFWError: (65550) b'X11: Platform not initialized'
  warnings.warn(message, GLFWError)
Traceback (most recent call last):
  File "/home/mark/git/wgpu-py/examples/triangle_glfw.py", line 18, in <module>
    device = main(canvas)
  File "/home/mark/git/wgpu-py/examples/triangle.py", line 67, in main
    return _main(canvas, device)
  File "/home/mark/git/wgpu-py/examples/triangle.py", line 84, in _main
    render_texture_format = present_context.get_preferred_format(device.adapter)
  File "/home/mark/miniforge3/envs/dev/lib/python3.10/site-packages/wgpu/backends/wgpu_native/_api.py", line 664, in get_preferred_format
    self._get_surface_id(), adapter._internal
  File "/home/mark/miniforge3/envs/dev/lib/python3.10/site-packages/wgpu/backends/wgpu_native/_api.py", line 368, in _get_surface_id
    self._surface_id = get_surface_id_from_canvas(self._get_canvas())
  File "/home/mark/miniforge3/envs/dev/lib/python3.10/site-packages/wgpu/backends/wgpu_native/_helpers.py", line 104, in get_surface_id_from_canvas
    surface_info = canvas.get_surface_info()
  File "/home/mark/miniforge3/envs/dev/lib/python3.10/site-packages/wgpu/gui/glfw.py", line 289, in get_surface_info
    "display": int(glfw.get_x11_display()),
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

the qt wayland example works!!!! and consequently my own personal application seems to be doing fine on intel + wayland.

almarklein commented 8 months ago

Do you recall the function we need to call from Qt's C++ API?

I mean this. It looks like this patch implements it for PySide. It's very recent, so not yet available in a release.

the qt wayland example works!!!!

Do you mean the qt triangle example, running on Wayland?

int(glfw.get_x11_display()),
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

I suppose this is related to glfw 3.4 having one binary that supports both X11 and Wayland, and the PYGLFW_LIBRARY_VARIANT is somehow not applied in the correct way. It does mean that wgpu is broken for glfw installed with conda, so that's unfortunate 😕

hmaarrfk commented 5 months ago

Notes to self: I think this is the structure that we need to get https://doc.qt.io/qt-6/qnativeinterface-qwaylandapplication.html

But QNativeInterface on qt 6.7.1 + pyside6 on conda-forge only exposes the X11 interface

from PySide6.QtGui import QNativeInterface
QNativeInterface.QX11Application

Will try to investigate -- https://bugreports.qt.io/browse/PYSIDE-2787

tfmoraes commented 1 month ago

Hi @hmaarrfk in the Pyside bug report there a new comment there. Thanks for helping with that, it can be very helpful.

About GLFW it's running very well on Wayland, i just needed to comment

    if "glfw" not in sys.modules:
        os.environ["PYGLFW_LIBRARY_VARIANT"] = "x11"

in gui/_gui_utils.py.

tfmoraes commented 1 month ago

Also, commenting os.environ["GDK_BACKEND"] = "x11" in gui/_gui_utils.py GLFW will use correct window decoration from Gnome.

almarklein commented 4 weeks ago

@tfmoraes what OS is that on, and have you install glfw via pip install glfwor are you somehow using a glfw lib provided by the system?

tfmoraes commented 4 weeks ago

@almarklein I'm using Fedora 41, Gnome 47 and Nvidia 4070 with proprietary drivers. I installed using pip install glfw. I think you need to install libdecor to have the Gnome window decoration. Also, this is the output of gui_glfw.py:

❯ python gui_glfw.py
Using GLFW with Wayland, which is experimental.
No config found!
EGL says it can present to the window but not natively
Max vertex attribute stride unknown. Assuming it is 2048
Re-initializing Gles context due to Wayland window
No config found!
EGL says it can present to the window but not natively
tfmoraes commented 4 weeks ago

Also, using GLFW just from pip package:

❯ cat /proc/76780/maps | grep -i glfw
7f2f6c893000-7f2f6c89c000 r--p 00000000 00:36 1253                       /tmp/lll/.venv/lib/python3.13/site-packages/glfw/wayland/libglfw.so
7f2f6c89c000-7f2f6c8b6000 r-xp 00009000 00:36 1253                       /tmp/lll/.venv/lib/python3.13/site-packages/glfw/wayland/libglfw.so
7f2f6c8b6000-7f2f6c8db000 r--p 00023000 00:36 1253                       /tmp/lll/.venv/lib/python3.13/site-packages/glfw/wayland/libglfw.so
7f2f6c8db000-7f2f6c8dd000 r--p 00047000 00:36 1253                       /tmp/lll/.venv/lib/python3.13/site-packages/glfw/wayland/libglfw.so
7f2f6c8dd000-7f2f6c8df000 rw-p 00049000 00:36 1253                       /tmp/lll/.venv/lib/python3.13/site-packages/glfw/wayland/libglfw.so