user1085907
user1085907

Reputation: 1035

2 QOpenGLWidget shared context causing crash

I would like to solve problem that I still deal with.. thats render 2 QOpenGLWidgets at same time in different top level windows with shared shader programs etc.

My first attempt was to use one context, wasnt working.

Is it even possible currently with QOpenGLWidget? Or I have to go to older QGLWidget? Or use something else?

testAttribute for Qt::AA_ShareOpenGLContexts returns true so there is not problem with sharing even QOpenGLContext::areSharing returns true. So there is something I missing or I dont know. Not using threads.

Debug output:

MapExplorer true true true QOpenGLShaderProgram::bind: program is not valid in the current context. MapExlorer paintGL ends MapExplorer true true true QOpenGLShaderProgram::bind: program is not valid in the current context. MapExlorer paintGL ends QOpenGLFramebufferObject::bind() called from incompatible context QOpenGLShaderProgram::bind: program is not valid in the current context. QOpenGLShaderProgram::bind: program is not valid in the current context. QOpenGLShaderProgram::bind: program is not valid in the current context. QOpenGLFramebufferObject::bind() called from incompatible context QOpenGLFramebufferObject::bind() called from incompatible context

MapView initializeGL:

void MapView::initializeGL()
{
    this->makeCurrent();

    initializeOpenGLFunctions();

    // Initialize World
    world->initialize(this->context(), size(), worldCoords);

    // Initialize camera shader
    camera->initialize();

    // Enable depth testing
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glDepthFunc(GL_LEQUAL); // just testing new depth func

    glClearColor(0.65f, 0.77f, 1.0f, 1.0f);
}

MapView paintGL:

void MapView::paintGL()
{
    this->makeCurrent();

    glDrawBuffer(GL_FRONT);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    world->draw(...);
}

MapExplorer initializeGL:

void MapExplorer::initializeGL()
{
    this->makeCurrent();

    QOpenGLContext* _context = _mapView->context();
    _context->setShareContext(this->context());
    _context->create();

    this->context()->create();
    this->makeCurrent();

    initializeOpenGLFunctions();

    // Enable depth testing
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glDepthFunc(GL_LEQUAL); // just testing new depth func

    glClearColor(0.65f, 0.77f, 1.0f, 1.0f);
}

MapExplorer paintGL:

void MapExplorer::paintGL()
{
    this->makeCurrent();

    qDebug() << "MapExplorer" << QOpenGLContext::areSharing(this->context(), _mapView->context()) << (QOpenGLContext::currentContext() == this->context());

    QOpenGLShaderProgram* shader = world->getTerrainShader();
    qDebug() << shader->create();
    shader->bind(); // debug error "QOpenGLShaderProgram::bind: program is not valid in the current context."

    // We need the viewport size to calculate tessellation levels and the geometry shader also needs the viewport matrix
    shader->setUniformValue("viewportSize",   viewportSize);
    shader->setUniformValue("viewportMatrix", viewportMatrix);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    qDebug() << "MapExlorer paintGL ends";

    //world->drawExplorerView(...);
}

Upvotes: 2

Views: 6371

Answers (3)

Neil Z. Shao
Neil Z. Shao

Reputation: 752

Hi I've been hacking this for 2 days, and finally got something to work.

The main reference is qt's threadrenderer example.

Basically I have a QOpenglWidget with its own context, and a background thread drawing to the context shared from QOpenglWidget. The framebuffer drawn by the background thread can be directly used by the QOpenglWidget.

Here's the steps to make things work:

  1. I have the QOpenglWidget RenderEngine and the background thread RenderWorker

    // the worker is a thread
    class RenderWorker : public QThread, protected QOpenGLFunctions
    {
        // the background thread's context and surface
        QOffscreenSurface *surface = nullptr;
        QOpenGLContext *context = nullptr;
    
        RenderWorker::RenderWorker()
        {
            context = new QOpenGLContext();
            surface = new QOffscreenSurface();
        }
    
        ...
    }
    
    // the engine is a QOpenglWidget
    class RenderEngine : public QOpenGLWidget, protected QOpenGLFunctions
    {
    protected:
        // overwrite
        void initializeGL() override;
        void resizeGL(int w, int h) override;
        void paintGL() override;
    
    private:
        // the engine has a background worker
        RenderWorker *m_worker = nullptr;
    
        ...
    }
    
  2. Create and setup the background thread in QOpenglWidget's initializeGL()

    void RenderEngine::initializeGL()
    {
        initializeOpenGLFunctions();
    
        // done with current (QOpenglWidget's) context
        QOpenGLContext *current = context();
        doneCurrent();
    
        // create the background thread
        m_worker = new RenderWorker();
    
        // the background thread's context is shared from current
        QOpenGLContext *shared = m_worker->context;
        shared->setFormat(current->format());
        shared->setShareContext(current);
        shared->create();
    
        // must move the shared context to the background thread
        shared->moveToThread(m_worker);
    
        // setup the background thread's surface
        // must be created here in the main thread
        QOffscreenSurface *surface = m_worker->surface;
        surface->setFormat(shared->format());
        surface->create();
    
        // worker signal
        connect(m_worker, SIGNAL(started()), m_worker, SLOT(initializeGL()));
    
        // must move the thread to itself
        m_worker->moveToThread(m_worker);
    
        // the worker can finally start
        m_worker->start();
    }
    
  3. The background thread have to initialize the shared context in its own thread

    void RenderWorker::initializeGL()
    {
        context->makeCurrent(surface);
        initializeOpenGLFunctions();
    }
    
  4. Now any framebuffer drawn in the background thread can be directly used (as texture, etc.) by the QOpenglWidget, e.g. in the paintGL() function.

As far as I know, an opengl context is kind of binded to a thread. Shared context and the corresponding surface must be created and setup in the main thread, moved to another thread and initialized in it, before it can be finally used.

Upvotes: 4

Bertrand
Bertrand

Reputation: 289

In your code I don't see where you link your shader program. You should:

  1. Remove shader->create() form paintGL, create it once in initializeGL function of one the views and share it among other views.
  2. link it in paintGL functions this way before binding it:

    if (!shader->isLinked())
        shader->link();
    

The link status of a shader program is context dependant (see OpenGL and take a look at QOpenGLShaderProgram::link() source code).

In the MapExplorer::initializeGL() remove _context (it's not used at all...). remove also this->context()->create (this is done by QOpenGLWidget).

In your main function put this at first line (or before QApplication instanciation) :

    QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

I do like this in a multi-QOpenGLWidget app and it works fine.

Upvotes: 1

steventaitinger
steventaitinger

Reputation: 402

One reason why your context is giving you grief is because you are trying to create your own in MapExplorer::initializeGL. QOpenGLWidget already creates its own context in its private initialize function. You need to use the one it creates. Its own context is also made current before each of initializeGL, paintGL and resizeGL. Making your own current is probably causing errors and is not how that widget is designed to be used.

Context sharing between widgets needs to be done with context.globalShareContext(). There is a static member of QOpenGLContext that gets initialized when QGuiApplication is created. That static member and the defaulFormat is what the QOpenGLWidgets context is initialized by automatically.

Upvotes: 0

Related Questions