关于QOpenGLWidget的多线程渲染

关于QOpenGLWidget的多线程渲染

背景介绍

我们知道,QOpenGLWidget是Qt中继承自QWidget的一个类,对OpenGL的渲染环境进行了很好的封装。用户在使用时只用继承并重写几个关键的虚函数即可完成OpenGL资源的准备和每帧画面的绘制。
但是,也正是因为QOpenGLWidget是继承自QWidget的一个类,也决定了它的绘制函数是在UI线程调用的,也就是说,当我们重写了QOpenGLWidgetpaintGL函数时,该函数是在UI线程被调用的。那么如果我们在paintGL函数里花太多时间进行场景绘制的话,会直接导致UI线程的更新不及时,鼠标键盘交互事件被阻塞,造成界面卡死的现象。
所以当我们选择QOpenGLWidget来在Qt界面中进行OpenGL场景绘制时,我们需要一种更高效的方式来保证场景能够正常渲染,且不会造成UI线程的阻塞。通常情况下,我们的可以选择的方式是在QOpenGLWidget中应用多线程渲染。

方案介绍

下面,我来介绍一种在我们产品中被使用,证明有效且高效的一种QOpenGLWidget多线程渲染处理方式。这套方案针对的应用场景是当我们的OpenGL场景需要按照一定的帧速率定时进行更新,且OpenGL场景的绘制需要消耗相当长的时间,可能超过16甚至33毫秒时。在这种场景下,OpenGL的渲染肯定会阻塞UI线程,造成画面卡点,交互卡顿。我们多线程OpenGL渲染的核心流程是这样的
首先,我们需要一个Renderer对象,专门负责核心的OpenGL场景渲染逻辑,并且我们需要一个Render Thread来和Renderer进行绑定,作为核心OpenGL场景的渲染线程。
其次,我们需要为Renderer创建一个和QOpenGLWidgetQOpenGLContext共享的新QOpenGLContext对象,用于在Render Thread中作为渲染所使用的上下文。
我们在Renderer进行渲染时, 需要将场景的结果渲染到一个OpenGL纹理上,而不是直接绘制到Render Buffer上。由于渲染线程的上下文和UI线程的OpenGL上下文共享,这个纹理可以在QOpenGLWidget被访问,并最终绘制到主FBO上用于显示。

方案流程

1. 下面是该方案主要使用的三个类的构成:

关于QOpenGLWidget的多线程渲染_第1张图片

2. 下面是该方案的主要运行流程:

关于QOpenGLWidget的多线程渲染_第2张图片

示例代码

头文件

#pragma once

#include 

class RefTexture {
   GLuint mTexture;
   QAtomicInt mRefCount;
private:
   ~RefTexture();

public:
   RefTexture(int width, int height);
   void retain();
   void release();
   GLuint value() const;
};

class GLWidget;
class GLWidgetRenderer :public QObject, public QOpenGLFunctions {
   Q_OBJECT
public:
   GLWidgetRenderer(GLWidget * player);
   void lockRenderer() { mRenderMutex.lock(); }
   void unlockRenderer() { mRenderMutex.unlock(); }
   void prepareExit() { mExiting = true; }
   void setGLContext(QOpenGLContext * context, QThread * thread);
   void stopRenderer();
   RefTexture * grabTexture();
public slots:
   void render();
Q_SIGNALS:
   void frameRenderStart();
   void frameRenderFinished();
   
private:
   RefTexture * mOutputTexture;
   QMutex mTextureLock;
   RefTexture * getWriteTexture(int width, int height);

   void setRendering(bool isRendering);
   bool isRendering();

private:
   bool mInit;
   QMutex mRenderStateMutex;
   bool mIsRenderingFrame;
   QThread *mThread;
   QOpenGLContext * mContext;
   QOffscreenSurface * mSurface;
   GLWidget *mGLWidget;
   QMutex mRenderMutex;
   bool mExiting;
};

class GLWidget :public QOpenGLWidget, public Core::GLBase {
   friend class GLWidgetRenderer;
   Q_OBJECT
private:
   int mSceneWidth, mSceneHeight;
   int mWidth, mHeight;

   QThread *mThread;
   GLWidgetRenderer *mRenderer;
   RefTexture * mOutputTexture;

   GLint mScreenFramebuffer;
   QOpenGLBuffer * mVbo;
   Core::GLShader * mScreenProgram;
   void prepareGLResource();

public:
   explicit GLWidget(int sceneWidth, int sceneHeight, QWidget *parent = 0);
   virtual ~GLWidget();
   void refreshFrame();
   void stopRendering();
   int sceneWidth() const;
   int sceneHeight() const;

signals:
   void renderRequested();

Q_SIGNALS:
   void playFinished();
   void playFrameBegin();
   void playFrameFinished();

private slots:
   void startRenderFrame();
   void finishedRenderFrame();

protected:
   void initializeGL() override;
   void resizeGL(int w, int h) override;
   void paintGL() override;
};

CPP文件

#include "GLWidget.h"

RefTexture::RefTexture(int width, int height) :
   mRefCount(1)
{
   auto functions = QOpenGLContext::currentContext()->functions();
   functions->glGenTextures(1, &mTexture);
   functions->glActiveTexture(GL_TEXTURE0);
   functions->glBindTexture(GL_TEXTURE_2D, mTexture);
   functions->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   functions->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   functions->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   functions->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   functions->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
   functions->glBindTexture(GL_TEXTURE_2D, 0);
}

RefTexture::~RefTexture() {
   QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &mTexture);
}

void RefTexture::retain() {
   mRefCount.fetchAndAddRelaxed(1);
}

void RefTexture::release() {
   if (1 == mRefCount.fetchAndAddOrdered(-1)) {
   	delete this;
   }
}

GLuint RefTexture::value() const {
   return mTexture;
}


GLWidgetRenderer::GLWidgetRenderer(GLWidget * player) :
   mGLWidget(player),
   mExiting(false),
   mContext(nullptr),
   mInit(false),
   mIsRenderingFrame(false),
   mOutputTexture(nullptr)
{
   QSurfaceFormat format;
   format.setDepthBufferSize(24);
   format.setStencilBufferSize(8);
   format.setRedBufferSize(8);
   format.setBlueBufferSize(8);
   format.setGreenBufferSize(8);
   format.setAlphaBufferSize(8);
   format.setVersion(2, 0);
   format.setProfile(QSurfaceFormat::CoreProfile);
   mSurface = new QOffscreenSurface();
   mSurface->setFormat(format);
   mSurface->create();
}

RefTexture * GLWidgetRenderer::getWriteTexture(int width, int height) {
   QMutexLocker lock(&mTextureLock);
   if (mOutputTexture)	{
   	mOutputTexture->release();
   }
   mOutputTexture = new RefTexture(width, height);
   return mOutputTexture;
}

RefTexture * GLWidgetRenderer::grabTexture() {
   QMutexLocker lock(&mTextureLock);
   if (isRendering() || mOutputTexture == nullptr) {
   	return nullptr;
   }
   else {
   	mOutputTexture->retain();
   	return mOutputTexture;
   }
}

void GLWidgetRenderer::setGLContext(QOpenGLContext * context, QThread * thread) {
   mContext = context;
   mThread = thread;
}


void GLWidgetRenderer::setRendering(bool isRendering) {
   QMutexLocker lock(&mRenderStateMutex);
   mIsRenderingFrame = isRendering;
}

bool GLWidgetRenderer::isRendering() {
   QMutexLocker lock(&mRenderStateMutex);
   return mIsRenderingFrame;
}

void GLWidgetRenderer::render() {
   if (mExiting) { return; }
   if (!mContext) { return; }

   QMutexLocker lock(&mRenderMutex);
   if (mExiting) { return; }

   bool ret = mContext->makeCurrent(mSurface);
   if (!mInit) {
   	mInit = true;
   	initializeOpenGLFunctions();
   	// 准备其他GL资源 
   }

   setRendering(true);

   if (outputTexture == nullptr) {
   	outputTexture = getWriteTexture(mGLWidget->sceneWidth(), mGLWidget->sceneHeight());
   	// 将outputTexture绑定到当前的FBO上面,用于存储渲染内容
   }
   emit frameRenderStart();
   // 渲染内容
   glFinish();
   emit frameRenderFinished();

   mContext->doneCurrent();
   setRendering(false);
   QMetaObject::invokeMethod(mGLWidget, "update");
}

void GLWidgetRenderer::stopRenderer() {
   // 销毁GL资源
}

GLWidget::GLWidget(int sceneWidth, int sceneHeight, QWidget *parent) :
   QOpenGLWidget(parent),
   mSceneWidth(sceneWidth),
   mSceneHeight(sceneHeight),
   mWidth(0),
   mHeight(0),
   mOutputTexture(nullptr)
{
   QSurfaceFormat format;
   format.setDepthBufferSize(24);
   format.setStencilBufferSize(8);
   format.setRedBufferSize(8);
   format.setBlueBufferSize(8);
   format.setGreenBufferSize(8);
   format.setAlphaBufferSize(8);
   format.setVersion(2, 0);
   format.setProfile(QSurfaceFormat::CoreProfile);
   setFormat(format);

   mThread = new QThread;
   mRenderer = new GLWidgetRenderer(this);
   mRenderer->moveToThread(mThread);
   connect(mThread, &QThread::finished, mRenderer, &QObject::deleteLater);
   connect(this, &GLWidget::renderRequested, mRenderer, &GLWidgetRenderer::render);
   connect(mRenderer, &GLWidgetRenderer::frameRenderStart, this, &GLWidget::startRenderFrame);
   connect(mRenderer, &GLWidgetRenderer::frameRenderFinished, this, &GLWidget::finishedRenderFrame);
   mThread->start();
}

GLWidget::~GLWidget() {
   stopRendering();
   mRenderer->prepareExit();
   mThread->quit();
   mThread->wait();
   delete mThread;
   makeCurrent();
   delete mFrameTimerLock;
   delete mFrameTimer;
   doneCurrent();
}

int GLWidget::sceneWidth() const{
   return mSceneWidth;
}

int GLWidget::sceneHeight() const{
   return mSceneHeight;
}

void GLWidget::initializeGL() {
   makeCurrent();
   glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mScreenFramebuffer);
   // 准备UI线程上的GL资源
   doneCurrent();

   QOpenGLContext * renderCtx = new QOpenGLContext();
   QSurfaceFormat format;
   format.setDepthBufferSize(24);
   format.setStencilBufferSize(8);
   format.setRedBufferSize(8);
   format.setBlueBufferSize(8);
   format.setGreenBufferSize(8);
   format.setAlphaBufferSize(8);
   format.setVersion(2, 0);
   format.setProfile(QSurfaceFormat::CoreProfile);
   renderCtx->setFormat(format);
   renderCtx->setShareContext(context());
   renderCtx->create();
   renderCtx->moveToThread(mThread);
   mRenderer->setGLContext(renderCtx, mThread);
}

void GLWidget::resizeGL(int w, int h) {
   mWidth = w * QApplication::desktop()->devicePixelRatio();
   mHeight = h * QApplication::desktop()->devicePixelRatio();
}

void GLWidget::startRenderFrame() {
   emit playFrameBegin();
}

void GLWidget::finishedRenderFrame() {
   emit playFrameFinished();
}

void GLWidget::paintGL() {
   mRenderer->lockRenderer();
   auto processTexture = mRenderer->grabTexture();
   makeCurrent();
   glViewport(0, 0, mWidth, mHeight);
   glClearColor(0, 0, 0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
   if (processTexture != nullptr || mOutputTexture != nullptr) {
   	if (processTexture != nullptr) {
   		if (mOutputTexture != nullptr) {
   			mOutputTexture->release();
   		}
   		mOutputTexture = processTexture;
   	}
   	// 将mOutputTexture绘制到主FBO上用于显示
   }
   doneCurrent();
   mRenderer->unlockRenderer();
}

void GLWidget::stopRendering() {
   mRenderer->lockRenderer();
   makeCurrent();
   mFrameTimer->stop();
   mRenderer->stopRenderer();
   // 删除UI线程上的GL资源
   if (mOutputTexture) {
   	mOutputTexture->release();
   	mOutputTexture = nullptr;
   }
   doneCurrent();
   mRenderer->unlockRenderer();
   emit playFinished();
}

void GLWidget::refreshFrame() {
   if (!isRendering()) {
   	emit renderRequested();
   }
}

你可能感兴趣的:(VE技术博客,Qt,多线程,QOpenGLWidget,OpenGL)