mosra/magnum

Request help when encountering strange problems about magnum-bootstrap-base-wxwidgets

fenggy opened this issue · 3 comments

I found a strange problem while using the wxwidgets project.When using the default empty project, the program normally has an empty window.
But when I add any Magnum variable (such as GL: Mesh _mesh), An assertion error will occur. as follows

    Context& Context::current() {
           // An error occurred
            CORRADE_ASSERT(currentContext, "GL::Context::current(): no current context", *currentContext);
            return *currentContext;
     }

I debugged the code and found class Mesh has Constructor function call.

   Mesh::Mesh(const MeshPrimitive primitive): _primitive{primitive}, _flags{ObjectFlag::DeleteOnDestruction} {
          Context::current().state().mesh.createImplementation(*this);
   }

So I moved the create of _glContexts from MainFrame to MyApplication, but still encountered a new initialization error with the following error code

   bool Context::tryCreate(const Configuration& configuration) {
           //Omit code ......
           #ifndef MAGNUM_TARGET_GLES2
            glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
            const auto versionNumberError = Renderer::error();
            if(versionNumberError == Renderer::Error::NoError) //If _glContext.create () is called in MainFrame, the logic is normal.
                 glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
             else
           #endif
            {
                   #ifndef MAGNUM_TARGET_GLES2
                          //If _glContext. create() is called in MyApplication, an error occurs here
                            CORRADE_ASSERT(versionNumberError == Renderer::Error::InvalidEnum,
                            "GL::Context: cannot retrieve OpenGL version:" << versionNumberError, false);
                   #endif
           //Omit code ......
           }

When strange, if _glContext.create (); The code is functioning normally when called in MainFrame.

Novices need your help, thank you

The test code is as follows

#include <Magnum/GL/DefaultFramebuffer.h>
#include <Magnum/Platform/GLContext.h>
#include <Magnum/GL/Buffer.h>
#include <Magnum/GL/Mesh.h>
#include <Magnum/GL/Renderer.h>
#include <Magnum/Math/Color.h>
#include <Magnum/MeshTools/Compile.h>
#include <Magnum/Primitives/Cube.h>
#include <Magnum/SceneGraph/Camera.h>
#include <Magnum/SceneGraph/Drawable.h>
#include <Magnum/SceneGraph/MatrixTransformation3D.h>
#include <Magnum/SceneGraph/Scene.h>
#include <Magnum/Shaders/PhongGL.h>
#include <Magnum/Trade/MeshData.h>
#include <Magnum/Shaders/VertexColorGL.h>

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

using namespace Magnum;

class MyApplication: public wxApp {
    public:
        MyApplication();
        Platform::GLContext _glContext;
        bool OnInit();
};

class MainFrame: public wxFrame {
    public:
        explicit MainFrame(int argc, char** argv);
        ~MainFrame();

    private:
        void OnPaint(wxPaintEvent& event);

        wxGLCanvas* _wxGlCanvas;
        wxGLContext* _wxGlContext;
        Platform::GLContext _glContext;
private:
	GL::Mesh _mesh;
//	Shaders::VertexColorGL2D _shader1;
};

wxIMPLEMENT_APP(MyApplication);


MyApplication::MyApplication():_glContext { NoCreate, argc, argv } 
{

}

bool MyApplication::OnInit() {
    //_glContext.create();
    MainFrame *frame = new MainFrame{argc, argv};
    frame->Show(true);
    return true;
}

MainFrame::MainFrame(int argc, char** argv): wxFrame{nullptr, wxID_ANY, "Magnum wxWidgets Application"}, _glContext{NoCreate, argc, argv} {
    wxBoxSizer* bSizer;
    bSizer = new wxBoxSizer{wxVERTICAL};

    #if (wxMAJOR_VERSION == 3) && (wxMINOR_VERSION >= 1)
    wxGLAttributes attributes;
    attributes.PlatformDefaults()
              .BufferSize(24)
              .MinRGBA(8, 8, 8, 0)
              .Depth(24)
              .Stencil(0)
              .DoubleBuffer()
              .EndList();
    _wxGlCanvas = new wxGLCanvas{this, attributes, wxID_ANY, wxDefaultPosition, wxSize{800, 600}};
    #elif (wxMAJOR_VERSION == 3) && (wxMINOR_VERSION == 0)
    int attributes[] = { WX_GL_RGBA,
                         WX_GL_DOUBLEBUFFER,
                         WX_GL_DEPTH_SIZE, 24,
                         WX_GL_STENCIL_SIZE, 0,
                         0 };
    _wxGlCanvas = new wxGLCanvas{this, wxID_ANY, &attributes[0], wxDefaultPosition, wxSize{800, 600}};
    #else
    #error You need wxWidgets version 3.0 or later.
    #endif

    _wxGlContext = new wxGLContext{_wxGlCanvas};
    Show();
    _wxGlCanvas->SetCurrent(*_wxGlContext);
    _glContext.create();

    bSizer->Add(_wxGlCanvas, 1, wxALL|wxEXPAND, 5);
    SetSizer(bSizer);

    Layout();

    bSizer->Fit(this);

    Centre(wxBOTH);

    _wxGlCanvas->Connect(wxEVT_PAINT, wxPaintEventHandler(MainFrame::OnPaint), nullptr, this);

    /* TODO: Add your initialization code here */
	//using namespace Math::Literals;

	//struct TriangleVertex {
	//	Vector2 position;
	//	Color3 color;
	//};
	//const TriangleVertex vertices[]{
	//	{{-0.5f, -0.5f}, 0xff0000_rgbf},    /* Left vertex, red color */
	//	{{ 0.5f, -0.5f}, 0x00ff00_rgbf},    /* Right vertex, green color */
	//	{{ 0.0f,  0.5f}, 0x0000ff_rgbf}     /* Top vertex, blue color */
	//};

	//_mesh.setCount(Containers::arraySize(vertices))
	//	.addVertexBuffer(GL::Buffer{ vertices }, 0,
	//		Shaders::VertexColorGL2D::Position{},
	//		Shaders::VertexColorGL2D::Color3{});

}

MainFrame::~MainFrame() {
    _wxGlCanvas->Disconnect(wxEVT_PAINT, wxPaintEventHandler(MainFrame::OnPaint), nullptr, this);
}

void MainFrame::OnPaint(wxPaintEvent& event) {
    GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);

    /* TODO: Add your drawing code here */
 //   _shader1.draw(_mesh);
    _wxGlCanvas->SwapBuffers();
}

`

environment
window11 vs2022

Hello!

The root cause here (besides the empty MyApplication constructor and second _glContext instance, which causes another assertion), boils down to the GL::Mesh constructed right during MainFrame construction, while the GL context is only created later, when _glContext.create(); is called. Thus you get an assertion, and the backtrace reveals that it indeed gets accessed too early. Same would happen if you'd uncomment _shader1.

The solution for this is to defer creation of all GL objects until after _glContext.create() is called. One option is to construct the members with NoCreate:

    private:
        ...
        Platform::GLContext _glContext;
        GL::Mesh _mesh{NoCreate}; // note the NoCreate
        Shaders::VertexColorGL2D _shader1{NoCreate}; // here also

and then explicitly creating them after the GL context is created inside MainFrame::MainFrame():

    _glContext.create();
    _mesh = GL::Mesh{}; // move actually created objects over these
    _shader1 = Shaders::VertexColorGL2D{}; // here also

But this gets tedious and error-prone with more GL objects (if you forget to create the NoCreate'd instances, you may get either broken rendering, or a crash, or just anything), so instead I suggest having something like this, putting the objects into a dedicated struct wrapped in an Optional object (or std::optional, if you want) that you construct after _glContext creation:

    private:
        ...
        Platform::GLContext _glContext;

        struct GLObjects {
            GL::Mesh mesh;
            Shaders::VertexColorGL2D shader1;
        };
        Containers::Optional<GLObjects> _glObjects;

and then in MainFrame::MainFrame():

    _glContext.create();
    _glObjects.emplace(); // create all GL objects

This also gives you an assertion if you accidentally forget to construct the contents, or attempt to access the contents too early, making it easier to discover mistakes.

I'm converting this issue to a discussion in case you have more questions.