f3d-app / f3d

Fast and minimalist 3D viewer.
https://f3d.app
BSD 3-Clause "New" or "Revised" License
2.89k stars 207 forks source link

Library doesn't work with Gtk under Wayland #1416

Open Nokse22 opened 6 months ago

Nokse22 commented 6 months ago

Describe the bug I tried to use libf3d with python and Gtk, but it doesn't render and it crashes

To Reproduce Steps to reproduce the behavior:

  1. Run the provided python code
  2. Open a 3d file
  3. It won't render and it will crash after resizing the window.

Expected behavior It should render correctly inside the Gtk.GLArea, if I render it outside it works.

System Information:

F3D Information The version of f3d library is 2.4.0 installed with pip

Additional context

import gi
import f3d

gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')

from gi.repository import Gtk, Adw

class F3Dwindow(Adw.ApplicationWindow):
    __gtype_name__ = 'F3Dwindow'

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.set_default_size(300, 300)
        self.set_title("F3D")

        self.gl_area = Gtk.GLArea(vexpand=True)
        self.gl_area.connect("realize", self.on_realize)
        self.gl_area.connect("render", self.on_render)

        box = Gtk.Box(orientation=1)

        button = Gtk.Button(label="open")
        button.connect("clicked", self.open_file_chooser)

        hb = Adw.HeaderBar()
        hb.pack_start(button)

        box.append(hb)
        box.append(self.gl_area)

        self.set_content(box)

        f3d.Engine.autoload_plugins()
        self.engine = f3d.Engine(f3d.Window.EXTERNAL)

    def on_realize(self, area):
        ctx = area.get_context()

    def on_render(self, area, ctx):
        ctx.make_current()
        self.engine.window.render()

        print(area.get_error())

        return True

    def open_file_chooser(self, *args):
        dialog = Gtk.FileDialog(
            title="Open File",
        )
        dialog.open(self, None, self.on_open_file_response)

    def on_open_file_response(self, dialog, response):
        file = dialog.open_finish(response)
        print(f"Selected File: {file.get_path()}")

        if file:
            filepath = file.get_path()
            self.engine.loader.load_geometry(filepath)

class F3Dapp(Adw.Application):
    def __init__(self):
        super().__init__()

    def do_activate(self):
        win = self.props.active_window
        if not win:
            win = F3Dwindow(application=self)
        win.present()

def main():
    app = F3Dapp()
    return app.run()

main()

This code should show an application window with a button to open a 3d model file and a Gtk.GLArea where to render the 3d model.

Every time it renders it prints this:

Current framebuffer is bind to framebuffer object 85
color attachment 0:
 this attachment is a texture with name: 166
 its mipmap level is: 0
 this is not a cube map texture.
 this is not 3D texture.
color attachment 1:
 this attachment is empty
color attachment 2:
 this attachment is empty
color attachment 3:
 this attachment is empty
color attachment 4:
 this attachment is empty
color attachment 5:
 this attachment is empty
color attachment 6:
 this attachment is empty
color attachment 7:
 this attachment is empty
depth attachment :
 this attachment is a texture with name: 167
 its mipmap level is: 0
 this is not a cube map texture.
 this is not 3D texture.
stencil attachment :
 this attachment is empty
there are 8 draw buffers. 
draw buffer[0]=GL_COLOR_ATTACHMENT0
draw buffer[1]=GL_NONE
draw buffer[2]=GL_NONE
draw buffer[3]=GL_NONE
draw buffer[4]=GL_NONE
draw buffer[5]=GL_NONE
draw buffer[6]=GL_NONE
draw buffer[7]=GL_NONE
read buffer=GL_COLOR_ATTACHMENT0

And the result of print(area.get_error()) at line 45 before crashing is:

Segmentation fault (core dumped)

Otherwise is None


I can correctly draw using OpenGL like this and it won't crash:

import gi
import f3d

from OpenGL.GL import *
from OpenGL.GLU import *

[...]

    def on_render(self, area, ctx):
        # self.engine.window.render()

        glClearColor(1,0,0,1)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        print(area.get_error())

        return True

[...]

If I do this:

[...]

    def on_render(self, area, ctx):
        img = self.engine.window.render_to_image()

        img.save("/home/user/image.png")

        print(area.get_error())

        return True

[...]

It will save an image of the same size of the Gtk.GLArea, but all black if no model is loaded or an image like the following one if a geometry is loaded.

image


With Gtk.GLArea I can set the allowed APIs with:

self.gl_area.set_allowed_apis(Gdk.GLAPI.GL)
self.gl_area.set_allowed_apis(Gdk.GLAPI.GLES)

With only GL it will not crash and it will not print anything, with GLES it will.

Meakk commented 6 months ago

Interesting! Thank you for sharing. I see nothing wrong in your code. I'll try to reproduce locally to take a closer look and let you know.

Nokse22 commented 6 months ago

Thank you very much!

Meakk commented 6 months ago

By default it seems like it's using GLES and I have a blank window. When adding self.gl_area.set_allowed_apis(Gdk.GLAPI.GL), GTK seems to complain it cannot create a GL context, any idea?

2024-04-30-111250_window

Nokse22 commented 6 months ago

I got that error only when setting the GLES api and required version above 3.3 with:

self.gl_area.set_allowed_apis(Gdk.GLAPI.GLES)
self.gl_area.set_required_version(4,0)

When setting only GL it never happened

I can confirm it is using GLES, adding print(area.get_api()) in on_render prints that is using GLES

snoyer commented 6 months ago

It's interesting that, as @Nokse22 already pointed out, the image from render_to_image() is the right size; we can also add print(self.engine.window.size) to on_render() to see that the engine does indeed resize with the window. So the the engine and the context/area/surface do get connected to some extent but then the actual rendering fails?

Nokse22 commented 6 months ago

I got it working but only on X11, the code is the same, I haven't set any preferred API or version for OpenGL and it doesn't crash. I thought it was only the app that didn't support Wayland.

Screenshot from 2024-05-01 00-23-21

Meakk commented 6 months ago

When using an external window type, libf3d does not handle the OpenGL surface so that's definitely a GTK issue. However I'm under X11 and it's not working on my side, but my issue looks unrelated to yours. Anyway, that looks good! Can I borrow your code to add it in the examples?

snoyer commented 6 months ago

Also not working on X11 here :( Maybe #1417 will help narrowing it down?

Nokse22 commented 6 months ago

Maybe https://github.com/f3d-app/f3d/pull/1417 will help narrowing it down?

Hopefully so I can also report it properly to GTK

When using an external window type, libf3d does not handle the OpenGL surface so that's definitely a GTK issue.

I will create an issue on GTK

However I'm under X11 and it's not working on my side, but my issue looks unrelated to yours.

That's too bad, I am working on a flatpak, when it's done I'll send it

Anyway, that looks good! Can I borrow your code to add it in the examples?

Of course! While testing I also made one with python and Qt f you want, it also only works in x11:

from PySide6.QtGui import QGuiApplication
from PySide6.QtOpenGL import QOpenGLWindow
import f3d

class F3DWindow(QOpenGLWindow):
    def __init__(self):
        super().__init__()
        self.mEngine = f3d.Engine(f3d.Window.Type.EXTERNAL)

        self.mEngine.loader.load_geometry("/path/to/geometry.obj")

    def paintGL(self):
        self.mEngine.window.render()
        print("rendering")

def main():
    a = QGuiApplication()
    w = F3DWindow()
    w.setTitle("F3D QT External Window")
    w.resize(300, 300)
    w.show()

    return a.exec_()

main()
Meakk commented 6 months ago

This example with Qt is working perfectly fine on my side (with X11). Thanks again for sharing, I'll also add it to the examples!

Nokse22 commented 6 months ago

More debugging

I have understood that the library uses GL and not GLES and the errors I got were because it was using GLES, forcing GL api on wayland fixes the errors, but it still doesn't render.

Wayland uses EGL as a backend while x11 uses GXL and this seems to be the issue, in fact using EGL on x11 causes the same issue. Unfortunately you can't use GXL in Wayland.

I still don't know if there is an issue in Gtk or something needs to be updated in the library to be able to use it in Wayland.

Using Gtk debug I was able to get the errors that points to some specific API calls

Errors ``` (exhibit:2): Gdk-CRITICAL **: 19:19:10.270: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_ENUM in glTexImage2DMultisample(internalformat=GL_NONE) (exhibit:2): Gdk-WARNING **: 19:19:10.270: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.270: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_NONE) (exhibit:2): Gdk-CRITICAL **: 19:19:10.270: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_NONE) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_NONE) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_NONE) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.271: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.271: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.275: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.275: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glClear(incomplete framebuffer) (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 0) (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 1) (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 2) (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glDrawArrays (exhibit:2): Gdk-CRITICAL **: 19:19:10.275: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 0) (exhibit:2): Gdk-WARNING **: 19:19:10.275: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.285: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glDrawArrays (exhibit:2): Gdk-WARNING **: 19:19:10.285: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-WARNING **: 19:19:10.285: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.285: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) (exhibit:2): Gdk-WARNING **: 19:19:10.285: OPENGL: Source: API Type: Other Severity: Medium Message: FBO incomplete: color attachment incomplete [0] (exhibit:2): Gdk-CRITICAL **: 19:19:10.285: OPENGL: Source: API Type: Error Severity: High Message: GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers) ```

Here is a summary of all the different error messages:

GL_INVALID_ENUM in glTexImage2DMultisample(internalformat=GL_NONE)
GL_INVALID_VALUE in glTexImage2D(internalFormat=GL_NONE)
FBO incomplete: color attachment incomplete [0]
GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers)
GL_INVALID_FRAMEBUFFER_OPERATION in glClear(incomplete framebuffer)
GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 0)
GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 1)
GL_INVALID_VALUE in glUniform1i(invalid sampler/tex unit index for uniform 2)
GL_INVALID_FRAMEBUFFER_OPERATION in glDrawArrays
FBO incomplete: color attachment incomplete [0]
GL_INVALID_FRAMEBUFFER_OPERATION in glBlitFramebuffer(incomplete draw/read buffers)
Nokse22 commented 5 months ago

I fixed it compiling all from source and adding VTK_OPENGL_HAS_EGL=ON that is enabled by default only on android, but Wayland uses EGL, so it was missing.

Compiling it this way, though will cause it to not work with X11 with GXL so I had to force EGL on X11 too. Maybe there is a way to make it work both with EGL and GXL.

qwertychouskie commented 5 months ago

From what I've read it's recommended to use EGL on X11 nowadays rather than GLX, e.g. Firefox switched to EGL on X11 a couple years ago or so.

mwestphal commented 4 months ago

related to: https://github.com/f3d-app/f3d/issues/1375

mwestphal commented 17 hours ago

Maybe there is a way to make it work both with EGL and GXL.

There is now! This will be much more easy with VTK 9.4.0 + F3D 3.0