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

标签 c++ multithreading qt opengl

我最近一直在苦苦挣扎,因为我不得不更改我不久前编写的一些代码以在 Qt 中进行图像处理和 OpenGl以支持多线程。
问题是我想用它在一组图像上应用批量过滤器,
我正在使用 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);
        }
    }
}
ImageProcessing::processImage()函数看起来像这样:
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);
}
OpenMP 时它工作正常已禁用,但是当我启用它时,出现以下错误:

Attempting to create QWindow-based QOffscreenSurface outside the gui thread. Expect failures.


根据文档,这是预期的行为,因为 QOffscreenSurface只是一个隐藏的QWindow在某些平台上,这意味着它只能从主线程创建或销毁。
所以为了创建QOffscreenSurface在主线程中,我关注了这个 answer并实现了以下类
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;
};
现在而不是直接创建一个 QOffscreenSurface在我的 processImage功能我执行以下操作:
OpenGlFilterPrivate openGlFilterPrivate;
openGlFilterPrivate.moveToMainThread();
QCoreApplication::postEvent(&openGlFilterPrivate, new QEvent(QEvent::Type(OpenGlFilterPrivate::CreateSurface)));
QSharedPointer<QOffscreenSurface> surface = openGlFilterPrivate.m_surface;
它有效,但现在我不断收到以下消息:

QObject::~QObject: Timers cannot be stopped from another thread


然后我的应用程序立即崩溃。
我知道任何 QObject包含 QTimer在内部,这就是导致问题的原因,但我想不出任何其他方法来解决这个问题。
有人能帮忙吗?
我需要做的就是使用 openGl 应用一些图像处理过滤器或任何其他硬件加速方法,同时有能力在 thread-safe 中做到这一点道路。
  • 我认为可以使用与 QPainter 相同的方法来完成,它是线程安全的,据我所知,它是使用 OpenGL 进行硬件加速的。但我找不到关于如何做这样的事情的任何资源。
  • 最佳答案

    哦,我的,OpenMP 和 OpenGL 可能不能很好地混合。 OpenMP 创建多线程的方式并没有固定在 OpenGL 实现可以访问的方面。它很可能使用常规 POSIX 线程或 Windows native 线程。但它也可以是其他任何东西,在同一地址空间内创建额外的任务。
    我认为创建一个由 N 个线程组成的池会更加健壮和容易,每个线程都有自己的共享 OpenGL 上下文,然后使用工作窃取来安排任务;使 OpenGL 在工作集之间的线程上保持最新状态。
    使用现代 OpenGL 非常方便,甚至不需要窗口或类似的可绘制对象来使上下文成为当前状态。 glXMakeContextCurrentwglMakeContextCurrent将接受 drawables/HDC 的 Nil 参数。因此,您可以对帧缓冲区对象和所有其他内容执行 OpenGL 操作,唯一需要注意的是,没有主要的默认帧缓冲区。
    随意使用这些代码片段来创建帮助器上下文。

    #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);
    }
    

    关于c++ - 我如何创建一个线程安全的 QOffscreenSurface 供 OpenGl 使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65560109/

    相关文章:

    c++ - 自定义派生的 std::exception 类的 'what' 函数返回神秘的废话

    c++ - 快速获取 "down-scale"三维张量索引的方法

    c# - 从任何类型的文档中获取文本

    python - 非阻塞服务器 Apache Thrift Python

    spring - 使用 RedisQueueInboundGateway 进行多线程处理

    c++ - 从 'int' 到 'const char*' 的无效转换

    c++ - 我可以在 OpenACC 中使用 std::bitset 的函数吗?

    python - 将代码的基本部分从 C++ 转换为 Python

    c - Windows 中的多线程 - 创建函数指针数组时出错

    CLion 中的 Qt5 QML - QQmlApplicationEngine 无法加载组件