wxNotebook can't handle wxGLCanvas correctly on Wayland (Issue #22580)

99 views
Skip to first unread message

tamasmeszaros

unread,
Jun 28, 2022, 8:51:07 AM6/28/22
to wx-...@googlegroups.com, Subscribed

Bug description
When a wxGLCanvas is inserted as one of the pages of a wxNotebook, the OpenGL window is visible on other pages as well.

Expected vs observed behaviour
The wxGLCanvas content with properly initialized OpenGL context and some rendered content is expected to be visible only when the appropriate page -- into which the canvas was inserted -- is selected.
The observation is that the OpenGL content is visible on other pages as well. This is only happening in a Wayland session with EGL support enabled. Setting GDK_BACKEND=x11 in such environment will hide the issue.

Patch or snippet allowing to reproduce the problem

#include <iostream>
#include <utility>
#include <memory>
#include <vector>

// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include <wx/tglbtn.h>
#include <wx/combobox.h>
#include <wx/msgdlg.h>
#include <wx/glcanvas.h>
#include <wx/notebook.h>

class Renderer {
protected:
    wxGLCanvas *m_canvas;
    std::unique_ptr<wxGLContext> m_context;
public:
    
    Renderer(wxGLCanvas *c): m_canvas{c} {
        m_context = std::make_unique<wxGLContext>(m_canvas);
    }
    
    wxGLContext * context() { return m_context.get(); }
    const wxGLContext * context() const { return m_context.get(); }

    // OpenGL example from https://cs.lmu.edu/~ray/notes/openglexamples/
    // section: Tetrahedron
    
    void set_active()
    {
        m_canvas->SetCurrent(*m_context);
        
        glClearColor(0.1, 0.39, 0.88, 1.0);
        glColor3f(1.0, 1.0, 1.0);

        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glFrustum(-2, 2, -1.5, 1.5, 1, 40);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0, 0, -3);
        glRotatef(50, 1, 0, 0);
        glRotatef(70, 0, 1, 0);
    }

    void draw_scene(long w, long h)
    {
        glViewport(0, 0, GLsizei(w), GLsizei(h));
        glClear(GL_COLOR_BUFFER_BIT);

        // Draw a white grid "floor" for the tetrahedron to sit on.
        glColor3f(1.0, 1.0, 1.0);
        glBegin(GL_LINES);
        for (GLfloat i = -2.5; i <= 2.5; i += 0.25) {
            glVertex3f(i, 0, 2.5); glVertex3f(i, 0, -2.5);
            glVertex3f(2.5, 0, i); glVertex3f(-2.5, 0, i);
        }
        glEnd();

        // Draw the tetrahedron.  It is a four sided figure, so when defining it
        // with a triangle strip we have to repeat the last two vertices.
        glBegin(GL_TRIANGLE_STRIP);
        glColor3f(1, 1, 1); glVertex3f(0, 2, 0);
        glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
        glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
        glColor3f(0, 0, 1); glVertex3f(0, 0, -1.4);
        glColor3f(1, 1, 1); glVertex3f(0, 2, 0);
        glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
        glEnd();

        glFlush();
    }

    void swap_buffers() { m_canvas->SwapBuffers(); }
};

// The top level frame of the application.
class MyFrame: public wxFrame
{
    wxGLCanvas     *m_canvas;
    std::unique_ptr<Renderer> m_renderer;
    
public:
    MyFrame(const wxString &       title,
            const wxPoint &        pos,
            const wxSize &         size);

    wxGLCanvas * canvas() { return m_canvas; }
    const wxGLCanvas * canvas() const { return m_canvas; }
};

class App : public wxApp {
    MyFrame *m_frame = nullptr;
    wxString m_fname;
public:
    bool OnInit() override {

        m_frame = new MyFrame("Wayland wxNotebook issue", wxDefaultPosition, wxSize(1024, 768));
        m_frame->Show( true );
        
        return true;
    }

};

wxIMPLEMENT_APP(App);

MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size):
    wxFrame(nullptr, wxID_ANY, title, pos, size)
{
    wxMenu *menuFile = new wxMenu;
    menuFile->Append(wxID_OPEN);
    menuFile->Append(wxID_EXIT);
    wxMenuBar *menuBar = new wxMenuBar;
    menuBar->Append( menuFile, "&File" );
    SetMenuBar( menuBar );

    auto notebookpanel = new wxPanel(this);
    auto notebook      = new wxNotebook(notebookpanel, wxID_ANY);
    auto maintab       = new wxPanel(notebook);

    m_canvas = new wxGLCanvas(maintab,
                              wxID_ANY,
                              nullptr,
                              wxDefaultPosition,
                              wxDefaultSize,
                              wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);

    m_renderer = std::make_unique<Renderer>(m_canvas);

    wxPanel *control_panel = new wxPanel(maintab);

    auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
    auto console_sizer = new wxBoxSizer(wxVERTICAL);

    std::vector<wxString> combolist = {"One", "Two", "Three"};
    auto combobox = new wxComboBox(control_panel, wxID_ANY, combolist[0],
                                   wxDefaultPosition, wxDefaultSize,
                                   int(combolist.size()), combolist.data());

    auto sz = new wxBoxSizer(wxHORIZONTAL);
    sz->Add(new wxStaticText(control_panel, wxID_ANY, "Choose number"), 0,
            wxALL | wxALIGN_CENTER, 5);
    sz->Add(combobox, 1, wxALL | wxEXPAND, 5);
    console_sizer->Add(sz, 0, wxEXPAND);

    auto btn1 = new wxToggleButton(control_panel, wxID_ANY, "Button1");
    console_sizer->Add(btn1, 0, wxALL | wxEXPAND, 5);

    auto btn2 = new wxToggleButton(control_panel, wxID_ANY, "Button2");
    btn2->SetValue(true);
    console_sizer->Add(btn2, 0, wxALL | wxEXPAND, 5);

    controlsizer->Add(console_sizer, 1, wxEXPAND);
    
    control_panel->SetSizer(controlsizer);
    
    auto maintab_sizer = new wxBoxSizer(wxHORIZONTAL);
    maintab_sizer->Add(m_canvas, 1, wxEXPAND);
    maintab_sizer->Add(control_panel, 0);
    maintab->SetSizer(maintab_sizer);

    notebook->AddPage(maintab, "Main");

    wxTextCtrl* textCtrl1 = new wxTextCtrl(notebook, wxID_ANY, L"Tab 2 Contents");
    notebook->AddPage(textCtrl1, "Dummy");

    auto notebooksizer = new wxBoxSizer(wxHORIZONTAL);
    notebooksizer->Add(notebook, 1, wxEXPAND);
    notebookpanel->SetSizer(notebooksizer);

    auto topsizer = new wxBoxSizer(wxHORIZONTAL);
    topsizer->Add(notebookpanel, 1, wxEXPAND);
    SetSizer(topsizer);
    SetMinSize(size);

    Bind(wxEVT_MENU, [this](wxCommandEvent &) {
        wxFileDialog dlg(this, "Select file",  wxEmptyString,
                         wxEmptyString, "*.*", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
        dlg.ShowModal();
    }, wxID_OPEN);

    Bind(wxEVT_MENU, [this](wxCommandEvent &) {
        Close();
    }, wxID_EXIT);

    Bind(wxEVT_SHOW, [this](wxShowEvent &) {
        m_renderer->set_active();

        m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &){
            wxPaintDC dc(m_canvas);

            const wxSize sz = m_canvas->GetClientSize();
            m_renderer->draw_scene(sz.x, sz.y);
            m_renderer->swap_buffers();
        });
    });
}

To Reproduce
Switch between the "Main" and "Dummy" tabs.

Platform and version information

  • wxWidgets version: 3.1.7 or 3.1.6 (where wxGLCanvasEGL is in use)
  • wxWidgets port: wxGTK
  • OS and its version: Manjaro Linux (or any other with a Wayland session)
  • GTK version: 3.24.34
  • GDK backend: Wayland


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580@github.com>

VZ

unread,
Jun 28, 2022, 9:31:34 AM6/28/22
to wx-...@googlegroups.com, Subscribed

Thanks for reporting this but unfortunately I don't think I'll have time to look at this before 3.2.0.

@swt2c Would you have any idea about this one by chance?


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1168729654@github.com>

Scott Talbert

unread,
Jun 28, 2022, 8:15:40 PM6/28/22
to wx-...@googlegroups.com, Subscribed

I can reproduce this. However, it's not immediately obvious to me what we're doing wrong or how to fix it...do we need to somehow tell the wayland surface to hide itself when it's containing wxGLCanvas is hidden?


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1169405406@github.com>

VZ

unread,
Oct 3, 2022, 7:06:55 PM10/3/22
to wx-...@googlegroups.com, Subscribed

I still have no idea what to do here, but here is the list of things that I've tried doing in "unmap" callback for wxGLCanvasEGL (which I've added) that do not work:

  • Call wl_surface_attach(surface, NULL) which looked like the best bet after reading

If wl_surface.attach is sent with a NULL wl_buffer, the following wl_surface.commit will remove the surface content.

in the documentation: this had no effect at all.

  • Same followed by wl_surface_commit(): visible flicker, but no other effect.
  • Calling wl_egl_window_resize(0, 0): no effect.
  • Calling wl_egl_window_destroy(): no effect (!?), followed by a crash when calling it again when destroying the window (as expected).
  • Destroy everything on unmap, recreate it on map: EGL contents disappears when switching to another page (yes!), but doesn't appear again when switching back to the first page (oops).

I'm really not sure whether it's a good idea to destroy the surface on unmap, but I don't understand at all why even doing this doesn't work.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1266176657@github.com>

Joan Bruguera

unread,
Aug 24, 2023, 12:44:37 PM8/24/23
to wx-...@googlegroups.com, Subscribed

Something that is most likely the same issue (but has a simpler reproducer): Calling canvas->Show(!canvas->IsShown()); on a wxGLCanvas will not hide the canvas on Wayland+EGL. It works with GDK_BACKEND=x11 and on wxMSW.

Reproducer:

#include <wx/app.h>
#include <wx/glcanvas.h>
#include <wx/dcclient.h>
#include <wx/frame.h>
#include <wx/button.h>
#include <wx/sizer.h>

class MyWxApp : public wxApp
{
public:
	bool OnInit() override {
		auto window = new wxFrame(nullptr, -1, wxT("Test"));

		auto canvas = new wxGLCanvas(window);
		auto context = new wxGLContext(canvas);
		canvas->Bind(wxEVT_PAINT, [context, canvas](wxPaintEvent& e) {
			fprintf(stderr, "Canvas paint\n");
			context->SetCurrent(*canvas);
			wxPaintDC dc(canvas);

			glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			canvas->SwapBuffers();
		});

		auto button = new wxButton(window, wxID_ANY, wxT("Toggle"));
		button->Bind(wxEVT_BUTTON, [window, canvas](wxCommandEvent& evt) {
			fprintf(stderr, "Shown is now %d\n", !canvas->IsShown());
			canvas->Show(!canvas->IsShown());
			window->Layout();
			window->Refresh(true);
		});

		auto sizer = new wxBoxSizer(wxHORIZONTAL);
		sizer->Add(button, 0, wxEXPAND);
		sizer->Add(canvas, 1, wxEXPAND);
		window->SetSizer(sizer);
		window->Layout();

		window->Show(true);
		SetTopWindow(window);
		return true;
	}
};

IMPLEMENT_APP(MyWxApp)


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1692055029@github.com>

VZ

unread,
Aug 24, 2023, 1:30:32 PM8/24/23
to wx-...@googlegroups.com, Subscribed

Thanks, this is indeed an even simpler, and hence better, test case. Unfortunately I still don't know what's going on here.

Maybe we really have to completely destroy and recreate the EGL surface when the window containing it is hidden/shown. Of course, the last time I tried I couldn't even manage to make this work, but it ought to be possible.

And I don't have any other ideas and my question didn't get any replies (not even wrong ones!).


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1692135671@github.com>

Joan Bruguera

unread,
Aug 24, 2023, 2:20:47 PM8/24/23
to wx-...@googlegroups.com, Subscribed

I'm not very familiar with the code but that's how I understand it as well. On Wayland the window created with wl_egl_window_create appears to be detached from the GDK window so it does not automatically hide along with the GDK window unlike on X11. On the GTK3 code it appears to do what you say, it registers a handler for the hide event and calls wl_egl_window_destroy there, and then re-creates the surface when it is shown again.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1692199796@github.com>

VZ

unread,
Aug 24, 2023, 2:31:23 PM8/24/23
to wx-...@googlegroups.com, Subscribed

I guess what I don't understand is what should be the actual GTK widget to an EGL surface. Currently we just create a generic wxWindow (wxPizza at GTK level) and this seems wrong, but I have no idea what would be right.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1692213395@github.com>

Joan Bruguera

unread,
Aug 24, 2023, 11:18:08 PM8/24/23
to wx-...@googlegroups.com, Subscribed

My attempt at solving the issue so far: joanbm@535b1b1, fixes both OP's and my reproducer.

It registers a signal for the GTK widget "map" (=show) event and moves part of the code from wxGLCanvasEGL::CreateSurface() there, and does the corresponding cleanup to destroy the surface on the "unmap" (=hide) event.

Before submitting a PR I want to do some more research to see if that is indeed the best/only way, plus more testing.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1692697668@github.com>

VZ

unread,
Aug 25, 2023, 8:32:36 AM8/25/23
to wx-...@googlegroups.com, Subscribed

My attempt at solving the issue so far: joanbm@535b1b1, fixes both OP's and my reproducer.

Great, thanks a lot!

It registers a signal for the GTK widget "map" (≅show) event and moves part of the code from wxGLCanvasEGL::CreateSurface() there, and does the corresponding cleanup to destroy the surface on the "unmap" (≅hide) event.

This seems very close to what I had tried originally, but I think I made the mistake of connecting to map/unmap on m_widget rather than m_wxwindow...

Before submitting a PR I want to do some more research to see if that is indeed the best/only way, plus more testing.

TIA!


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1693285497@github.com>

Joan Bruguera

unread,
Aug 29, 2023, 11:45:02 PM8/29/23
to wx-...@googlegroups.com, Subscribed

I submitted a PR (#23835) with a slightly more polished fix.

I guess what I don't understand is what should be the actual GTK widget to an EGL surface. Currently we just create a generic wxWindow (wxPizza at GTK level) and this seems wrong, but I have no idea what would be right.

I guess any widget would work since we just paint over it, we don't really use it other than for layout and events. Not sure if there's anything more barebones.

This seems very close to what I had tried originally, but I think I made the mistake of connecting to map/unmap on m_widget rather than m_wxwindow...

Actually, in this case AFAICT, m_widget == m_wxwindow, so it must have been something else.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1698443488@github.com>

VZ

unread,
Sep 5, 2023, 8:48:02 PM9/5/23
to wx-...@googlegroups.com, Subscribed

Closed #22580 as completed via 952de60.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issue/22580/issue_event/10289403186@github.com>

Ian McInerney

unread,
Sep 22, 2023, 9:55:14 AM9/22/23
to wx-...@googlegroups.com, Subscribed

@vadz will you have a chance to include this in the 3.2.3 backports?


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1731464988@github.com>

VZ

unread,
Sep 22, 2023, 10:32:05 AM9/22/23
to wx-...@googlegroups.com, Subscribed

Yes, thanks for the reminder, I'll add it to #23891 soon.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22580/1731526290@github.com>

Reply all
Reply to author
Forward
0 new messages