简体   繁体   English

我如何创建一个线程安全的 QOffscreenSurface 供 OpenGl 使用

[英]How can i create a thread-safe QOffscreenSurface to be used by OpenGl

I've been struggling lately when i had to change some code that i wrote a while ago to do image processing in Qt and OpenGl to support multithreading.我最近一直在苦苦挣扎,因为我不得不更改我不久前编写的一些代码以在QtOpenGl中进行图像处理以支持多线程。

the problem is that I want to use it to apply batches of filters on a set of images, i'm using openMP to do the multi-threading like this:问题是我想用它在一组图像上应用批量过滤器,我正在使用 openMP 来执行这样的多线程:

void batchProcess(QVector<QImage> &images)
{
    #pragma omp parallel
    {
    #pragma omp for schedule(dynamic) nowait
        for (int i = 1; i < images.count(); i++)
        {
            images[i] = ImageProcessing::processImage(images[i], _vertexShader, _fragmentShader, _textureVar, _vertexPosVar, _textureCoordVar);
        }
    }
}

and the ImageProcessing::processImage() function looks like this: ImageProcessing::processImage() function 看起来像这样:

QImage ImageProcessing::processImage(const QImage &source, const QString &vertexShader, const QString &fragmentShader, const QString &textureVar, const QString &vertexPosVar, const QString &textureCoordVar)
{
    QOpenGLContext context;
    if(!context.create())
        return source;

    QOffscreenSurface surface;
    surface.setFormat(context.format());
    surface.create();
    if(!surface.isValid())
        return source;

    if(!context.makeCurrent(&surface))
        return source;

    QOpenGLFramebufferObject fbo(source.size());
    context.functions()->glViewport(0, 0, source.width(), source.height());

    QOpenGLShaderProgram program(&context);
    if (!program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader))
        return source;

    if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader))
        return source;

    if (!program.link())
        return source;

    if (!program.bind())
        return source;

    QOpenGLTexture texture(QOpenGLTexture::Target2D);
    texture.setData(source);

    texture.bind();
    if(!texture.isBound())
        return source;

    VertexData vertices[] =
    {
        {{ -1.0f, +1.0f }, { 0.0f, 1.0f }}, // top-left
        {{ +1.0f, +1.0f }, { 1.0f, 1.0f }}, // top-right
        {{ -1.0f, -1.0f }, { 0.0f, 0.0f }}, // bottom-left
        {{ +1.0f, -1.0f }, { 1.0f, 0.0f }}  // bottom-right
    };

    GLuint indices[] =
    {
        0, 1, 2, 3
    };

    QOpenGLBuffer vertexBuf(QOpenGLBuffer::VertexBuffer);
    QOpenGLBuffer indexBuf(QOpenGLBuffer::IndexBuffer);

    if(!vertexBuf.create())
        return source;

    if(!indexBuf.create())
        return source;

    if(!vertexBuf.bind())
        return source;

    vertexBuf.allocate(vertices, 4 * sizeof(VertexData));

    if(!indexBuf.bind())
        return source;
        
    indexBuf.allocate(indices, 4 * sizeof(GLuint));

    int offset = 0;
    program.enableAttributeArray(vertexPosVar.toLatin1().data());
    program.setAttributeBuffer(vertexPosVar.toLatin1().data(), GL_FLOAT, offset, 2, sizeof(VertexData));
    offset += sizeof(QVector2D);
    program.enableAttributeArray(textureCoordVar.toLatin1().data());
    program.setAttributeBuffer(textureCoordVar.toLatin1().data(), GL_FLOAT, offset, 2, sizeof(VertexData));
    program.setUniformValue(textureVar.toLatin1().data(), 0);

    context.functions()->glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, Q_NULLPTR);

    return fbo.toImage(false);
}

it works fine when OpenMP is disabled, but when i enable it i get the following error:禁用OpenMP时它工作正常,但是当我启用它时,我收到以下错误:

Attempting to create QWindow-based QOffscreenSurface outside the gui thread.试图在 gui 线程之外创建基于 QWindow 的 QOffscreenSurface。 Expect failures.期待失败。

according to the documentation, this is an expected behavior, because the QOffscreenSurface is just a hidden QWindow on some platforms, which means that it can only be created or destroyed from the main thread.根据文档,这是一个预期的行为,因为QOffscreenSurface在某些平台上只是一个隐藏的QWindow ,这意味着它只能从主线程创建或销毁。

so in order to create the QOffscreenSurface in the Main thread i followed this answer and implemented the following class所以为了在主线程中创建QOffscreenSurface我遵循了这个答案并实现了以下 class

class OpenGlFilterPrivate : public QObject
{
    Q_OBJECT
public:
    enum CustomEventTypes
    {
        CreateSurface = QEvent::User,
        CreateContext = QEvent::User + 1,
        CreateProgram = QEvent::User + 2
    };

    void moveToMainThread()
    {
        moveToThread(QApplication::instance()->thread());
    }

    virtual bool event(QEvent *event )
    {
        switch (event->type())
        {
        case CreateSurface: // m_surface (create an offscreen surface from the main thread)
            m_surface = QSharedPointer<QOffscreenSurface>::create();

            m_surface->setFormat(m_context->format());
            m_surface->create();
            break;

        case CreateContext: // m_context (create an openGl context from the main thread)
            m_context = QSharedPointer<QOpenGLContext>::create();
            break;

        case CreateProgram: // m_shaderProgram (create an openGl shader program from the main thread)
            m_shaderProgram = QSharedPointer<QOpenGLShaderProgram>::create(&*m_context);
        }

        return false;
    }

    QSharedPointer<QOpenGLContext> m_context;
    QSharedPointer<QOffscreenSurface> m_surface;
    QSharedPointer<QOpenGLShaderProgram> m_shaderProgram;
};

and now instead of directly creating a QOffscreenSurface in my processImage function i do the following:现在,我不是直接在我的processImage function 中创建QOffscreenSurface ,而是执行以下操作:

OpenGlFilterPrivate openGlFilterPrivate;
openGlFilterPrivate.moveToMainThread();
QCoreApplication::postEvent(&openGlFilterPrivate, new QEvent(QEvent::Type(OpenGlFilterPrivate::CreateSurface)));
QSharedPointer<QOffscreenSurface> surface = openGlFilterPrivate.m_surface;

it works, but now i keep getting the following message:它有效,但现在我不断收到以下消息:

QObject::~QObject: Timers cannot be stopped from another thread QObject::~QObject: 定时器不能从另一个线程停止

and then my application crashes immediately.然后我的应用程序立即崩溃。

i know that any QObject contains a QTimer internally and that this is what's causing the problem, but i can't think of any other way to get around this.我知道任何QObject在内部都包含一个QTimer ,这就是导致问题的原因,但我想不出任何其他方法来解决这个问题。

Can anyone help with this?有人能帮忙吗? all i need to do is to apply some image processing filters using openGl or any other hardware accelerated method and at the same time have the ability to do it in a thread-safe way.我需要做的就是使用openGl或任何其他硬件加速方法应用一些图像处理过滤器,同时能够以thread-safe方式进行。

  • i think it can be done using the same method used by QPainter , it's thread safe and as far as i know it's hardware accelerated using OpenGl.我认为它可以使用QPainter使用的相同方法来完成,它是线程安全的,据我所知,它是使用 OpenGl 进行硬件加速的。 but i couldn't find any resources on how to do such thing.但我找不到任何关于如何做这件事的资源。

Oh my, OpenMP and OpenGL probably don't mix very well.哦,我的,OpenMP 和 OpenGL 可能混合得不太好。 The way OpenMP creates multiple threads is not pinned down in terms that would be accessible to OpenGL implementations. OpenMP 创建多线程的方式并没有固定在 OpenGL 实现可以访问的方面。 Most likely it's using regular POSIX threads or Windows native threads.很可能它使用常规 POSIX 线程或 Windows 本机线程。 But it could also be anything else, that creates additional tasks inside the same address space.但它也可以是其他任何东西,在同一地址空间内创建额外的任务。

I think it'd be far more robust and easier to tackle to create a pool of N threads, each with its own sharing OpenGL context and then use work stealing to schedule the tasks;我认为创建一个由 N 个线程组成的池会更加健壮和更容易解决,每个线程都有自己的共享 OpenGL 上下文,然后使用工作窃取来安排任务; keep the OpenGL current on the threads between worklets.使 OpenGL 在工作集之间的线程上保持最新。

Conveniently enough with modern OpenGL one doesn't even need a window or similar drawable to make a context current.使用现代 OpenGL 足够方便,甚至不需要 window 或类似的可绘制对象来使上下文电流。 glXMakeContextCurrent and wglMakeContextCurrent will accept Nil parameters for the drawables/HDCs. glXMakeContextCurrentwglMakeContextCurrent将接受可绘制对象/HDC 的 Nil 参数。 So you can do OpenGL operations on framebuffer objects and all the other stuff, with the only caveat being, that there's no main default framebuffer.因此,您可以对帧缓冲区对象和所有其他内容执行 OpenGL 操作,唯一需要注意的是,没有主要的默认帧缓冲区。

Feel free to use those code snippets for creating the helper contexts.随意使用这些代码片段来创建帮助程序上下文。

#include "wglu_context.h"
#include <windows.h>
#include <GL/gl.h>
#include <GL/wglext.h>

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <assert.h>

static char const *wgl_create_context_attribs_name = "wglCreateContextAttribsARB";

static
int wglu_get_proc_address(char const *name, PROC *pproc)
{
    int rc= 0;
    *pproc = wglGetProcAddress(name);
    fprintf(stderr, "%s: %p\n", name, (void*)*pproc );
    if( !(*pproc) ){
        rc= GetLastError();
        fprintf(stderr,
            "error wglGetProcAddress('%s'): 0x%x\n",
            name, rc);
    }
    if( rc ){ fprintf(stderr, "%s: %d\n", __func__, rc); }
    return rc;
}

/* -----------------------------------------------------------------------
 * Create a OpenGL context of desired version and share it */
int wglu_create_context_with_sharing(
    int major, int minor, int profile,
    HDC   surface,
    HGLRC share_context,
    HGLRC *out_context )
{
    int rc= 0;
    int attribs[8] = {0};
    size_t i_attrib = 0;
    HGLRC context = 0;

    HDC   const save_surface = wglGetCurrentDC();
    HGLRC const save_context = wglGetCurrentContext();
    if( save_surface != surface
     || save_context != share_context
    ){
        wglMakeCurrent(surface, share_context);
    }

    PFNWGLCREATECONTEXTATTRIBSARBPROC wgl_create_context_attribs;
    if( (rc= wglu_get_proc_address(
            wgl_create_context_attribs_name,
            (PROC*)&wgl_create_context_attribs))
    ){
        goto fail;
    }

    if( major ){
        attribs[i_attrib++] = WGL_CONTEXT_MAJOR_VERSION_ARB;
        attribs[i_attrib++] = major;
    }
    if( minor ){
        attribs[i_attrib++] = WGL_CONTEXT_MINOR_VERSION_ARB;
        attribs[i_attrib++] = minor;
    }
    if( profile ){
        attribs[i_attrib++] = WGL_CONTEXT_PROFILE_MASK_ARB;
        attribs[i_attrib++] = profile;
    }
    attribs[i_attrib] = attribs[i_attrib+1] = 0;

    context = wgl_create_context_attribs(surface, *share_context, attribs);
    if( !context ){
        rc= GetLastError();
        fprintf(stderr,
            "error %s(surface=0x%x, share_context=0x%x, attribs=0x%p): %x\n",
            wgl_create_context_attribs_name,
            (uintptr_t)surface, (uintptr_t)share_context,
            attribs,
            rc );
        goto fail;
    }

    if( !(wglMakeCurrent(surface, context)) ){
        rc= GetLastError();
        fprintf(stderr,
            "error %s(surface=0x%x, contest=0x%x): 0x%x\n",
            "wglMakeCurrent",
            (uintptr_t)surface, (uintptr_t)context,
            rc );
        goto fail;
    }
    assert( context == wglGetCurrentContext() );
    fprintf(stderr,
        "GL_VENDOR   = %s\n"
        "GL_RENDERER = %s\n"
        "GL_VERSION  = %s\n",
        glGetString(GL_VENDOR),
        glGetString(GL_RENDERER),
        glGetString(GL_VERSION) );

    if( !(wglMakeCurrent(NULL, NULL)) ){
        rc= GetLastError();
        fprintf(stderr,
            "error %s(0, 0): 0x%x\n",
            "wglMakeCurrent",
            (uintptr_t)surface, (uintptr_t)context,
            rc );
        goto fail;
    }
    if( !(wglShareLists(context, share_context)) ){
        rc= GetLastError();
        fprintf(stderr,
            "error %s(context=0x%x, share_context=0x%x): 0x%x\n",
            "wglShareLists",
            (uintptr_t)context, (uintptr_t)share_context,
            rc );
        goto fail;
    }

    wglMakeCurrent(save_surface, save_context);

    if( !rc ){
        if( out_context ){ *out_context = context; }
    } else {
        if( context ){ wglDeleteContext(context); }
    }

    if( rc ){ fprintf(stderr, "%s: %d\n", __func__, rc); }
    return rc;
}
#include <GL/glxext.h>

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <errno.h>
#include <dlfcn.h>

static char const *glX_create_context_attribs_name = "glXCreateContextAttribsARB";

typedef void (*PROC)();

static
int glxu_get_proc_address(char const *name, PROC *pproc)
{
    int rc= 0;
    static PROC (*glX_get_proc_address)(char const*) = NULL;
    if( !glX_get_proc_address ){
        *(void**)(&glX_get_proc_address) = dlsym(RTLD_DEFAULT, "glXGetProcAddress");
    }
    if( !glX_get_proc_address ){
        rc = -EFAULT;
    } else {
        *pproc = glX_get_proc_address(name);
        fprintf(stderr, "%s: %p\n", name, (void*)*pproc );
        if( !(*pproc) ){
            rc= -ENOENT;
            fprintf(stderr, "error glXGetProcAddress('%s'): so such function\n", name);
        }
    }
    if( rc ){ fprintf(stderr, "%s: %d\n", __func__, rc); }
    return rc;
}

static
int glxu_get_fbconfig_for_xid(
    Display *display,
    GLXFBConfigID const id,
    GLXFBConfig *out_fbconfig )
{
    static GLXFBConfig* (*glX_get_fb_configs)(Display*,int,int*) = NULL;
    static int          (*glX_get_fb_config_attrib)(Display*,GLXFBConfig,int,int*) = NULL;
    if( !glX_get_fb_configs ){
        *(void**)(&glX_get_fb_configs) = dlsym(RTLD_DEFAULT, "glXGetFBConfigs");
    }
    if( !glX_get_fb_config_attrib ){
        *(void**)(&glX_get_fb_config_attrib) = dlsym(RTLD_DEFAULT, "glXGetFBConfigAttrib");
    }

    int rc = 0;
    int n_configs = 0;
    /* we always assume to operate on screen 0, since hardly any X connection
     * encountered these days actually operates on multiple screens. */
    if( !glX_get_fb_configs || !glX_get_fb_config_attrib ){
        rc = -EFAULT;
    } else {
        GLXFBConfig *const fbconfig = glX_get_fb_configs(display, 0, &n_configs);
        for(int i=0; !rc && i < n_configs; ++i){
            unsigned int qry_id;
            rc= glX_get_fb_config_attrib(display, fbconfig[i], GLX_FBCONFIG_ID, &qry_id);
            if( !rc && id == qry_id ){
                *out_fbconfig = fbconfig[i];
                break;
            }
        }
    }
    return rc;
}

Display *glxu_get_current_display(void)
{
    static Display* (*glX_get_current_display)(void) = NULL;
    if( !glX_get_current_display ){
        *(void**)(&glX_get_current_display) = dlsym(RTLD_DEFAULT, "glXGetCurrentDisplay");
    }
    if( !glX_get_current_display ){
        return NULL;
    }
    return glX_get_current_display();
}

GLXDrawable glxu_get_current_drawable(void)
{
    static GLXDrawable (*glX_get_current_drawable)(void) = NULL;
    if( !glX_get_current_drawable ){
        *(void**)(&glX_get_current_drawable) = dlsym(RTLD_DEFAULT, "glXGetCurrentDrawable");
    }
    if( !glX_get_current_drawable ){
        return 0;
    }
    return glX_get_current_drawable();
}

/* -----------------------------------------------------------------------
 * Create a OpenGL context of desired version and share it */
int glxu_create_context_with_sharing(
    int major, int minor, int profile,
    Display *display,
    GLXDrawable surface,
    GLXContext share_context,
    GLXContext *out_context )
{
    int rc= 0;
    int attribs[8] = {0};
    size_t i_attrib = 0;
    GLXContext context = 0;
    unsigned int fbconfigxid = 0;
    GLXFBConfig fbconfig = 0;

    static GLXContext  (*glX_get_current_context)(void)  = NULL;
    static void        (*glX_query_drawable)(Display*,GLXDrawable,int,unsigned*)  = NULL;
    static Bool        (*glX_make_current)(Display*,Drawable,GLXContext) = NULL;
    static void        (*glX_destroy_context)(Display*,GLXContext) = NULL;
    if( !glX_get_current_context ){
        *(void**)(&glX_get_current_context) = dlsym(RTLD_DEFAULT, "glXGetCurrentContext");
    }
    if( !glX_query_drawable ){
        *(void**)(&glX_query_drawable) = dlsym(RTLD_DEFAULT, "glXQueryDrawable");
    }
    if( !glX_make_current ){
        *(void**)(&glX_make_current) = dlsym(RTLD_DEFAULT, "glXMakeCurrent");
    }
    if( !glX_destroy_context ){
        *(void**)(&glX_destroy_context) = dlsym(RTLD_DEFAULT, "glXDestroyContext");
    }

    if( !glX_get_current_context || !glX_query_drawable
     || !glX_make_current        || !glX_destroy_context
    ){
        return -EFAULT;
    }

    Display     *const save_display = glxu_get_current_display();
    GLXDrawable  const save_surface = glxu_get_current_drawable();
    GLXContext   const save_context = glX_get_current_context();

    if( !display ){ display = save_display; }
    if( !surface ){ surface = save_surface; }
    if( !share_context ){ share_context = save_context; }

    if( save_display != display
     || save_surface != surface
     || save_context != share_context
    ){
        fprintf(stderr, ".....\n");
        if( !(glX_make_current(display, surface, share_context)) ){
            rc = -1;
            goto fail;
        }
    }

    PFNGLXCREATECONTEXTATTRIBSARBPROC glX_create_context_attribs;
    if( (rc= glxu_get_proc_address(
            glX_create_context_attribs_name,
            (PROC*)&glX_create_context_attribs))
    ){
        rc = -2;
        goto fail;
    }

    glX_query_drawable(display, surface, GLX_FBCONFIG_ID, &fbconfigxid);
    glxu_get_fbconfig_for_xid(display, fbconfigxid, &fbconfig);

    if( major ){
        attribs[i_attrib++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
        attribs[i_attrib++] = major;
    }
    if( minor ){
        attribs[i_attrib++] = GLX_CONTEXT_MINOR_VERSION_ARB;
        attribs[i_attrib++] = minor;
    }
    if( profile ){
        attribs[i_attrib++] = GLX_CONTEXT_PROFILE_MASK_ARB;
        attribs[i_attrib++] = profile;
    }
    attribs[i_attrib] = attribs[i_attrib+1] = 0;

    context = glX_create_context_attribs(display, fbconfig, share_context, True, attribs);
    if( !context ){
        rc= -3;
        fprintf(stderr,
            "error %s(surface=0x%p, share_context=0x%p, attribs=0x%p): %x\n",
            glX_create_context_attribs_name,
            (void*)surface, (void*)share_context,
            attribs,
            rc );
        goto fail;
    }

    if( !(glX_make_current(display, surface, context)) ){
        rc= -4;
        fprintf(stderr,
            "error %s(surface=0x%p, contest=0x%p): 0x%x\n",
            "wglMakeCurrent",
            (void*)surface, (void*)context,
            rc );
        goto fail;
    }
    assert( context == glX_get_current_context() );
    fprintf(stderr,
        "GL_VENDOR   = %s\n"
        "GL_RENDERER = %s\n"
        "GL_VERSION  = %s\n",
        glGetString(GL_VENDOR),
        glGetString(GL_RENDERER),
        glGetString(GL_VERSION) );

fail:
    glX_make_current(display, save_surface, save_context);

    if( !rc ){
        if( out_context ){ *out_context = context; }
    } else {
        if( context ){ glX_destroy_context(display, context); }
    }

    if( rc ){ fprintf(stderr, "%s: %d\n", __func__, rc); }
    return rc;
}

Bool glxu_make_context_current(
    Display* display,
    GLXDrawable read, GLXDrawable draw,
    GLXContext ctx )
{
    static Bool (*glX_make_context_current)(Display*,Drawable,Drawable,GLXContext) = NULL;
    if( !glX_make_context_current ){
        *(void**)(&glX_make_context_current) = dlsym(RTLD_DEFAULT, "glXMakeContextCurrent");
    }
    if( !glX_make_context_current ){
        return False;
    }
    return glX_make_context_current(display, read, draw, ctx);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM