Creating a windowless GL Context on a separate thread on Wayland (Issue #2)

48 views
Skip to first unread message

Gonzalo Garramuño

unread,
Dec 8, 2022, 4:55:12 PM12/8/22
to fltkc...@googlegroups.com
In my viewer, I create thumbnails on a separate thread, by creating a
class that inherits from Fl_Gl_Window but it is never shown.  I'll show
the code I have for X11 (which works) and the one for Wayland (which
doesn't).
My first issue is that fl_wl_xid(this) returns nullptr, at least until
the window is shown.  This is different that what happens with macOS,
X11 or Windows.

I cannot show the window in the separate thread.  If I call show() in
the initThread() function, the code proceeds, but it fails in the

    ThumbnailCreator::ThumbnailCreator() :
        Fl_Gl_Window( 1, 1 ),
        running( false ),
        thread( nullptr  )
  {
        mode( FL_RGB | FL_ALPHA | FL_OPENGL3 );
        border(0);
        end();

        // We create a window but we never show it, as we just need the
        // basics of setting it to OpenGL3.  We replace the GL context
in the run() method so
        // that it is in the right thread.
    }

    // this gets called in the main thread
    void
    ThumbnailCreator::initThread()
    {
        if ( running ) return;

#ifdef FLTK_USE_WAYLAND
        // show();  // this maps the window and will return a valid
fl_wl_xid(this)
#endif

        if ( !thread )
        {
          running = true;
          thread  = new std::thread( &ThumbnailCreator::run, this );
        }
    }

    void ThumbnailCreator::run()
    {

#if defined(FLTK_USE_X11)
        if ( fl_x11_display() )
        {
            GLXContext ctx = glXCreateContext(fl_x11_display(),
fl_visual, NULL,
                                              GL_TRUE);
            this->context( ctx, true );
            glXMakeCurrent(fl_x11_display(), fl_x11_xid(this), ctx);
        }
#endif

#if defined(FLTK_USE_WAYLAND)
        wl_display* wld = fl_wl_display();
        if (wld)
        {
            wld_window*  win  = fl_wl_xid(this);
            if ( !win )
            {
                std::cerr << "No window" << std::endl;
                return;
            }
            wl_surface* surface = fl_wl_surface(win);
            if ( !surface )
            {
                std::cerr << "No surface" << std::endl;
                return;
            }

            // Wayland specific code here
            EGLint numConfigs;
            EGLint majorVersion;
            EGLint minorVersion;
            EGLConfig config;
            EGLint fbAttribs[] =
                {
                    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
                    EGL_RED_SIZE,        8,
                    EGL_GREEN_SIZE,      8,
                    EGL_BLUE_SIZE,       8,
                    EGL_ALPHA_SIZE,      8,
                    EGL_NONE
                };
            EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE, EGL_NONE };

            EGLDisplay egl_display =
eglGetDisplay((EGLNativeDisplayType) wld);
            if (egl_display == EGL_NO_DISPLAY) {
                std::cerr << "Can't create egl display" << std::endl;
                return;
            }

            if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) {
                std::cerr << "Can't initialise egl display" << std::endl;
                return;
            }

            if ( (eglGetConfigs(egl_display, NULL, 0, &numConfigs) !=
                  EGL_TRUE) || (numConfigs == 0))
            {
                std::cerr << "No Configuration..." << std::endl;
                return;
            }

            if ( (eglChooseConfig(egl_display, fbAttribs, &config, 1,
                                  &numConfigs) != EGL_TRUE) ||
                 (numConfigs != 1))
            {
                std::cerr << "No Chosen Configuration..." << std::endl;
                return;
            }

            GLContext ctx = eglCreateContext( egl_display, config,
                                              EGL_NO_CONTEXT,
contextAttribs );
            if ( ctx == EGL_NO_CONTEXT )
            {
                std::cerr << "No context...\n" << std::endl;
                return;
            }

            this->context( ctx, true );

            if ( ! eglMakeCurrent( egl_display, surface, surface, ctx ) )
            {
                std::cerr << "Could not make the current window current"
                          << std::endl;
                return;
            }

        }
#endif

        // call GLAD to bind the GL functions
        tl::gl::initGLAD();

        // create the thumbnail here....

}

Manolo

unread,
Dec 9, 2022, 1:25:51 AM12/9/22
to fltk.coredev
Le jeudi 8 décembre 2022 à 22:55:12 UTC+1, ggar...@gmail.com a écrit :
In my viewer, I create thumbnails on a separate thread, by creating a
class that inherits from Fl_Gl_Window but it is never shown.  I'll show
the code I have for X11 (which works) and the one for Wayland (which
doesn't).
My first issue is that fl_wl_xid(this) returns nullptr, at least until
the window is shown.  This is different that what happens with macOS,
X11 or Windows.

That's wrong.
 fl_xid() and its new variants fl_wl_xid(), fl_x11_xid(), … return NULL before the window is shown
and non-nul after.


I cannot show the window in the separate thread. 
Do you mean that you create the GL3 window in a child thread and then show it
in the main thread?
Why don't make both in the main thread?
 ……
I see you duplicate wayland-specific code here that is already in the FLTK library.
Why?

 

Gonzalo Garramuño

unread,
Dec 9, 2022, 2:34:34 AM12/9/22
to fltkc...@googlegroups.com


El 9/12/22 a las 03:25, Manolo escribió:


Le jeudi 8 décembre 2022 à 22:55:12 UTC+1, ggar...@gmail.com a écrit :
In my viewer, I create thumbnails on a separate thread, by creating a
class that inherits from Fl_Gl_Window but it is never shown.  I'll show
the code I have for X11 (which works) and the one for Wayland (which
doesn't).
My first issue is that fl_wl_xid(this) returns nullptr, at least until
the window is shown.  This is different that what happens with macOS,
X11 or Windows.

That's wrong.
 fl_xid() and its new variants fl_wl_xid(), fl_x11_xid(), … return NULL before the window is shown
and non-nul after.

You are right.  On X11 however, I can have:

            glXMakeCurrent(fl_x11_display(), fl_x11_xid(this), ctx);

with a null fl_x11_xid and OpenGL3 multithreading will work.  On macOS I can create a context without any window. 



I cannot show the window in the separate thread. 
Do you mean that you create the GL3 window in a child thread and then show it
in the main thread?
Why don't make both in the main thread?

No, the window creation happens in the main thread but the window is never shown (mapped).  I create a new OpenGL context in the worker thread, so OpenGL will work on that thread.  The OpenGL3 drawing happens on this separate thread to create a thumbnail, not on the draw() function.

Once the thumbnail is rendered into a Fl_RGB_Image, it is returned to the main thread to use as it wants.

I have code for macOS, Windows and X11 works.   I now have also code for Wayland that works, but it needs the GL window to be shown first on the main thread, as I need to do:


wld_window*  win  = fl_wl_xid(this);
wl_surface*       surface = fl_wl_surface(win);

I would like to obtain a wl_surface() without showing the window.

I see you duplicate wayland-specific code here that is already in the FLTK library.
Why?

Because the GL context needs to be created in the worker thread to work with OpenGL multithreaded.  I cannot just call make_current() on the worker thread without a crash.  Sadly, FLTK does not expose the Wayland's egl internal pointers and config.  If it did I could reduce the code to just two calls, like on X11 (creating the GL context and making it current).

That's why I need to do all this (this is tested and works):


      wl_display* wld = fl_wl_display();

      if ( wld )
      {

            wld_window*  win  = fl_wl_xid(this);  // this works of course if I show the window on the main thread


            if ( !win )
            {
                std::cerr << "No window" << std::endl;
                return;
            }

            surface = fl_wl_surface(win);
            if ( !surface )
            {
                std::cerr << "No surface" << std::endl;
                return;
            }

            egl_display = eglGetDisplay((EGLNativeDisplayType) wld);
            if (egl_display == EGL_NO_DISPLAY) {
                std::cerr << "Can't create egl display" << std::endl;
                return;
            }

           egl_window = wl_egl_window_create(surface, pixel_w(), pixel_h() );

           if ( egl_window == EGL_NO_SURFACE )
           {
               std::cerr << "Could not create egl window" << std::endl;


               return;
           }

            // Wayland specific code here
            EGLint numConfigs;

            EGLint major, minor;


            EGLConfig config;
            EGLint fbAttribs[] =
                {
                    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
                    EGL_RED_SIZE,        8,
                    EGL_GREEN_SIZE,      8,
                    EGL_BLUE_SIZE,       8,
                    EGL_ALPHA_SIZE,      8,

                    EGL_DEPTH_SIZE,      0,
                    EGL_SAMPLE_BUFFERS,  0,
                    EGL_STENCIL_SIZE,    0,


                    EGL_NONE
                };
            EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL_NONE, EGL_NONE };


            if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) {
                std::cerr << "Can't initialise egl display" << std::endl;
                return;
            }

            if ( (eglGetConfigs(egl_display, NULL, 0, &numConfigs) !=
                  EGL_TRUE) || (numConfigs == 0))
            {
                std::cerr << "No Configuration..." << std::endl;
                return;
            }

            eglBindAPI(EGL_OPENGL_API);



            if ( (eglChooseConfig(egl_display, fbAttribs, &config, 1,
                                  &numConfigs) != EGL_TRUE) ||
                 (numConfigs != 1))
            {
                std::cerr << "No Chosen Configuration..." << std::endl;
                return;
            }

            egl_surface = eglCreateWindowSurface(egl_display, config,
                                                 egl_window, NULL);
            if ( !egl_surface  )
            {
                std::cerr << "Could not create egl surface" << std::endl;
                return;
            }

            auto ctx = eglCreateContext( egl_display, config,


                                         EGL_NO_CONTEXT, contextAttribs );
            if ( ctx == EGL_NO_CONTEXT )
            {
                std::cerr << "No context...\n" << std::endl;
                return;
            }

            if ( ! eglMakeCurrent( egl_display, egl_surface,
                                   egl_surface, ctx ) )


            {
                std::cerr << "Could not make the current window current"
                          << std::endl;
                return;
            }

      }

and at the end of the thread:


#if defined(FLTK_USE_WAYLAND)
        if ( wld )
        {
            eglDestroySurface( egl_display, egl_surface );
            wl_egl_window_destroy( egl_window );
        }
#endif

imm

unread,
Dec 9, 2022, 3:40:31 AM12/9/22
to fltkc...@googlegroups.com
OK.
TBH I didn't follow all of that but here goes anyway:
Firstly, (it is documented that) you can not create nor show any
window from a child thread, but I think Gonzalo has taken account of
that.

Secondly, I think I'd be looking at creating an offscreen surface
(from the context of the main thread) and then using that to draw the
thumbnails and etc., rather than having an actual window (though I
haven't tried that recently with GL, I have done something equivalent
using standard fltk drawing calls and it was fine.)
AFAIK the Fl_Surface mechanism should be able to support a GL
offscreen context (though the "legacy" fl_offscreen mechanism was
always a bit broken with GL.)

Manolo

unread,
Dec 9, 2022, 5:20:43 AM12/9/22
to fltk.coredev
Le vendredi 9 décembre 2022 à 08:34:34 UTC+1, Gonzalo a écrit :


El 9/12/22 a las 03:25, Manolo escribió:

I have code for macOS, Windows and X11 works.   I now have also code for Wayland that works, but it needs the GL window to be shown first on the main thread, as I need to do:


wld_window*  win  = fl_wl_xid(this);
wl_surface*       surface = fl_wl_surface(win);

I would like to obtain a wl_surface() without showing the window.

I agree with the public FLTK API, it's not possible to get a wl_surface without showing the corresponding window.
You may proceed as follows , though, accessing internal, Wayland-specific data structures :

#include "src/drivers/Wayland/Fl_Wayland_Screen_Driver.H" // requires an adequate -I at compile time

  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
  struct wl_surface *my_wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor);

to create a wl_surface without Fl_Window. I don't guarantee that this wl_surface is usable, though.

I see you duplicate wayland-specific code here that is already in the FLTK library.
Why?

Because the GL context needs to be created in the worker thread to work with OpenGL multithreaded.  I cannot just call make_current() on the worker thread without a crash.  Sadly, FLTK does not expose the Wayland's egl internal pointers and config.  If it did I could reduce the code to just two calls, like on X11 (creating the GL context and making it current).

That's why I need to do all this (this is tested and works):

………

I don't know OpenGL so can't comment on all this.

If you identify what exactly you need be exposed in terms of Wayland internal data and how it's similarly
exposed in X11, I can try to add public Wayland-specific accessor functions of the type fl_wl_XXX()
just as fl_wl_glcontext().

Manolo

unread,
Dec 9, 2022, 5:28:11 AM12/9/22
to fltk.coredev
Le vendredi 9 décembre 2022 à 11:20:43 UTC+1, Manolo a écrit :
I agree with the public FLTK API, it's not possible to get a wl_surface without showing the corresponding window.
You may proceed as follows , though, accessing internal, Wayland-specific data structures :

#include "src/drivers/Wayland/Fl_Wayland_Screen_Driver.H" // requires an adequate -I at compile time

  Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
  struct wl_surface *my_wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor);

to create a wl_surface without Fl_Window. I don't guarantee that this wl_surface is usable, though.

and also, if you create a wl_surface like that, you'll need Wayland function wl_surface_destroy() to destroy it after use.

Gonzalo Garramuño

unread,
Dec 9, 2022, 5:42:30 AM12/9/22
to fltkc...@googlegroups.com


El 9/12/22 a las 07:28, Manolo escribió:

Excellent!  That worked perfectly.  You may want to create a public function in FL/wayland.H to create a surface so that I don't need to point the include to the src/ directory.

wl_surface* fl_wl_create_surface();


Gonzalo Garramuño

unread,
Dec 9, 2022, 6:16:03 AM12/9/22
to fltkc...@googlegroups.com

El 9/12/22 a las 07:42, Gonzalo Garramuño escribió:
>
>
> El 9/12/22 a las 07:28, Manolo escribió:
>
> Excellent!  That worked perfectly.  You may want to create a public
> function in FL/wayland.H to create a surface so that I don't need to
> point the include to the src/ directory.
>
> wl_surface* fl_wl_create_surface();
>
Or better yet, you might want to return the current compositor, like:

wl_compositor* fl_wl_compositor();

Manolo

unread,
Dec 9, 2022, 11:08:45 AM12/9/22
to fltk.coredev
Le vendredi 9 décembre 2022 à 12:16:03 UTC+1, Gonzalo a écrit :

Or better yet, you might want to return the current compositor, like:

wl_compositor* fl_wl_compositor(); 

Done at commit eb2572d .

Gonzalo Garramuño

unread,
Dec 10, 2022, 4:57:55 PM12/10/22
to fltkc...@googlegroups.com

I found out it is possible under Wayland and X11 to open a gl context
without any window, by using pBuffers.  In macOS, this is the case
too.   Only under Windows you need to use glew or similar to use
pBuffers, so I use a dummy window which I never show.

I am attaching the code to OffscreenContext, for OpenGL 3, which you may
find useful if you are doing multithreaded OpenGL or Offscreen rendering
to a FBO.

The way you use the class (simplified) is like this:

class ThumbnailCreator
{

    mrv::OffscreenContext context;
    bool running = false;

public:
    ThumbnailCreator()    {};
    ~ThumbnailCreator() {};

    void initThread();

    void runThread();

};

void ThumbnailCreator::initThread()
{
    context.init()

   running = true;

   //  ..start thread here calling runThread..

}

void ThumbnailCreator::runThread()
{

   context.make_current();

   while ( running )
  {

   // Draw OpenGL3 here offscreen

   }

   context.release();

}

mrvGLOffscreenContext.cpp
mrvGLOffscreenContext.h
Util.h
Reply all
Reply to author
Forward
0 new messages