Moderngl (python) with GTK4

[Note: this post was flagged as spam. There is no such thing in this post, it actually seems to be due to the filters set which consider posting messages of certain length within a certain amount of time, specially if there are links in them. Below is the original message without further edits]

Hello,

I’ve tried to use moderngl https://github.com/moderngl/moderngl/tree/main with GTK4. The closest thing they have to an example is at https://github.com/moderngl/moderngl/issues/84 with GTK3 where a cairo GTK.DrawingArea is drawing function is used to call moderngl rendering functions

I’ve tried to do the same, using a DrawingArea with GTK4 unsuccessfully, even though I can draw with cairo just fine; I’ve also tried using a GTK.GLArea but I suspect it won’t work because moderngl creates it’s own framebuffer which ends up not being the framebuffer used by GTK4 for display, but that’s really just an educated guess

When moderngl creates it’s context it does so based on an existing pure OpenGL context, and it doesn’t complain when I call it from the GTK.ApplicationWindow realize callback, so I’m guessing that’s not the problem

Any pointers about how to get GTK4 and moderngl to play nice with each other?

This is my minimal non-working code so far:

import gi
gi.require_version('Gtk', '4.0'); from gi.repository import Gtk
import moderngl

def on_activate(app):
    drawing_area = Gtk.DrawingArea()
    drawing_area.connect('realize', on_realize)
    drawing_area.set_draw_func(on_draw)
    win = Gtk.ApplicationWindow(application=app)
    win.set_child(drawing_area)
    win.present()

def on_realize(gldrawingarea):
#    global gl_context
#    gl_context = glarea.get_display().create_gl_context()
#    gl_context.make_current()
    global ctx
    ctx = moderngl.create_context()

def on_draw(glarea, cairorender, w, h):
    ctx.clear(0, 1, 0)

gl_context = None
ctx = None
app = Gtk.Application()
app.connect('activate', on_activate)
app.run(None)

You cannot create a random GL context and expect things to work: GTK expects to be in charge of the GL context creation, and you can only draw on a GTK widget using the GL context that GTK creates.

It’s understandable that uncoordinated code won’t work

My understanding is that there’s not ‘the GL context’ but that GTK manages multiple contexts. GTK draws with OpenGL by default and that would need a top-level context and each GLArea has a context. Either way, moderngl doesn’t create a new OpenGL context (even if they their creation ‘context’) but instead incorporates an existing context. It will not work and complain if no context has been already created

I suspect that if I don’t explicitly make a GLArea’s context the current one, it will use the top level context, but even if I call GLArea.make_current() it won’t work. If I don’t use GLArea at all, and instead create a gl context myself with GTK.DrawingArea.get_display().create_gl_context() it won’t work either, even if I set export GSK_RENDERER=cairo in bash

I also found in moderngl’s docs that it detects the framebuffer too

I’d be happy if I could use moderngl to draw on the window without targeting an specific widget, though it feels that if I could, I would break GTK somehow. I’d also be happy if I could use the context GTK creates with moderngl. pyopengl works but that’s just a machine-generated binding so if it didn’t work GLArea would be broken

moderngl can be used with wxpython, qt, GTK3, kivy and others. I find it hard to believe there is no way to make it work with GTK4

Looking at https://github.com/moderngl/moderngl/blob/e9e7cd3c8040a7e24876ab4a4cb13180ec292118/moderngl/__init__.py#L1493 the default alpha is 0.0, so it will be invisible.

Also, since it appears the library uses existing OpenGL context (“create context” seems to refer to the moderngl library Context object), it is probably simpler to use Gtk.GLArea since it already sets stuff up, so I only need to do ctx.detect_framebuffer() every frame to keep it updated (like for viewport changes).

Edit: FYI there is also a github issue about this I want to link this thread with.

Here is some code I experimented with:

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
import moderngl


class ModernGLArea(Gtk.GLArea):

    def __init__(self):
        super().__init__()
        self.ctx = None
        self.connect('render', self._setup_render)

    def _setup_render(self, area, context):
        if self.ctx is None:
            self.ctx = moderngl.create_context()

        fb = self.ctx.detect_framebuffer()
        fb.use()
        return False  # Keep going the signal chain

    def do_unrealize(self):
        self.ctx = None
        Gtk.GLArea.do_unrealize(self)


def on_draw(area, context):
    if area.ctx is None:
        print("ctx is None!")
        return True

    global checkbox
    if checkbox.props.active:
        area.ctx.clear(0, 1, 0, 1)
    else:
        area.ctx.clear(0, 0, 0, 1)
    print(area.ctx.viewport, area.ctx.fbo.glo)
    return False


checkbox = None


def on_activate(app):
    win = Gtk.ApplicationWindow(application=app)
    box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
    area = ModernGLArea()
    area.connect('render', on_draw)
    area.props.hexpand = True
    area.props.vexpand = True
    box.append(area)
    global checkbox
    checkbox = Gtk.CheckButton.new_with_label("Some example widget")
    checkbox.connect('notify::active', lambda obj, prop: area.queue_draw())
    box.append(checkbox)
    win.set_child(box)
    win.present()


app = Gtk.Application()
app.connect('activate', on_activate)
app.run(None)

Looking at moderngl/moderngl/__init__.py at e9e7cd3c8040a7e24876ab4a4cb13180ec292118 · moderngl/moderngl · GitHub the default alpha is 0.0, so it will be invisible

That’s a great catch, it’s possible one of my previous attempts worked. I’ve been using clear(0,1,0) under the false assumption the default alpha was 1 (to overwrite the screen by default), and that moderngl’s glEnable(GL_BLEND) had to be called too, but it seems it’s enabled by default on moderngl .

I guess I’m going back to check my previous attempts with GLArea now, and maybe experiment with ctx.detect_framebuffer()

This was solved between this thread and the issue linked by @Neui . The following is a shortened version of what I wrote in the issue:

ModernGL works with gtk4 when using GLArea, as long as you initialize ModernGL’s context in the do_realize() inherited function or the render callback, but not in the realize or create-context callbacks. GL_BLEND is enabled by default. Like it was mentioned, since the default alpha for clearing is 0, if you don’t specify the alpha it will look like clearing had no effect

However with PyGObject during the realize callback neither GLArea.make_current(), GLArea.get_context().make_current() or Gdk.GLContext.make_current() work. There’s already another current context in that callback, but it’s not the one for the GLArea, maybe the top-level OpenGL context. I’m not sure where the problem lies given that the c api docs for GLArea have an example calling make_current() in the realize callback

Unlike gtk3, creating a OpenGL context within a Gtk.DrawingArea is not a registered method to use ModernGL with gtk4. Calling moderngl.create_context() works well in the render callback or in do_realize if you subclass GLArea and call Gtk.GLArea.do_realize(self)

I wanted to mark this as solved in the title but I don’t think I can edit the title Not sure if I should count it as solved yet.

Thanks for all the help

Can’t confirm, it appears to work (but I am also using attach_buffers() which automatically calls make_current(), and binds the correct framebuffer to let moderngl try to use, also compare the fb.glo number - for me the correct one starts at 1), but the viewport size is (0, 0, 0, 0), but then do_resize() is called which then has the correct viewport size (but you still need to update moderngl viewport size which is what I am doing with that detect_framebuffer() thing it “suggests”).

def do_realize_cb(area: ModernGLArea) -> bool:
    if area.ctx is None:
        area.attach_buffers()
        area.ctx = moderngl.create_context()
        # Use the provided framebuffer by the GLArea
        fb = area.ctx.detect_framebuffer()
        fb.use()
        print("do_realize_cb", area.ctx, fb, fb.glo, fb.viewport)
    return False
# ...
area.connect('realize', do_realize_cb)

Can’t confirm, it appears to work (but I am also using attach_buffers() which [automatically calls make_current()]

I checked again, and it’s working now. Either I was looking at the output from moderngl, or the platform package issues were causing it

I tried a few things. Calling GLArea.make_current() actually changes the output of Gdk.GLContext.get_current(), and GLArea.attach_buffers() changes the output of PyOpenGL.GL.glGetIntegerv( PyOpenGL.GL.GL_DRAW_FRAMEBUFFER_BINDING )

but the viewport size is (0, 0, 0, 0) […] then do_resize() is called which then has the correct viewport size

I ran into the viewport issue on my own. In realize or in the realize callback, even if I run make_current() or attach_buffers(), I’ll always get a viewport of (0,0,0,0)

moderngl.create_context() in do_realize() will give a context that can clear the GLArea, but in the realize callback it won’t no matter what you do in that callback.

I also found it is not necessary to call fb = ctx.detect_framebuffer().use() if you create moderngl’s context after using GLArea.attach_buffers()

The only remaining odd thing is that calling glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING ) after calling attach_buffers() directly will crash in python, both in Linux and msys2, but only the first time if using the python debugger. The most curious thing is that with the gtk4 c api, and querying GL_DRAW_FRAMEBUFFER_BINDING after attach_buffers() works fine . But! that is either a python binding issue (which I doubt since it’s done with GObject introspection) or a PyOpenGL issue, and unlikely to trigger during normal use

There’s a way that will always work, even in the callbacks, with only three lines:

  1. Call GLArea.attach_buffers(). This only seems to be needed if the moderngl context is created in a callback
  2. Create a moderngl context after GLArea has been realized (or the parent if inside a subclass)
  3. Call the moderngl’s context detect_framebuffer().use() function inside the do_resize() function

Welp, it seems it’s not ModernGL’s default behavior to blend, but instead GTK4, probably when merging the GLArea, and window’s framebuffers

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.