About QOpenGLWidget multi-threaded rendering

About QOpenGLWidget multi-threaded rendering

Background introduction

We know that it QOpenGLWidgetis a class inherited from QWidget in Qt, which encapsulates the OpenGL rendering environment. Users only need to inherit and rewrite a few key virtual functions to complete the preparation of OpenGL resources and the drawing of each frame.
However, it is precisely because of QOpenGLWidgetthat a class inherits from QWidget, but also determines its function is to draw on the UI thread, that is, when we rewrote QOpenGLWidgetthe paintGLtime function, which is to be called on the UI thread . So if we paintGLspend too much time in the function to draw the scene, it will directly cause the UI thread to be updated not in time, the mouse and keyboard interaction events are blocked, and the interface is stuck.
So when we choose QOpenGLWidgetto draw an OpenGL scene in the Qt interface, we need a more efficient way to ensure that the scene can be rendered normally without blocking the UI thread. Under normal circumstances, our choice is QOpenGLWidgetto apply multi-threaded rendering in.

An Introduction

Next, let me introduce a QOpenGLWidgetmulti-threaded rendering processing method that is used in our products and has proven effective and efficient . The application scenario targeted by this solution is when our OpenGL scene needs to be updated regularly at a certain frame rate, and the drawing of the OpenGL scene takes a long time, which may exceed 16 or even 33 milliseconds. In this scenario, OpenGL rendering will definitely block the UI thread, causing screen freezes and interactive freezes. The core process of our multi-threaded OpenGL rendering is like this.
First, we need a Renderer object, which is responsible for the core OpenGL scene rendering logic, and we need a Render Thread to bind to the Renderer as the rendering thread of the core OpenGL scene.
Secondly, we need to create a Renderer and QOpenGLWidgeta QOpenGLContextnew shared QOpenGLContextobject as a context for rendering used in Render Thread in.
When we render in the Renderer, we need to render the result of the scene to an OpenGL texture instead of directly drawing it to the Render Buffer. Since the context of the rendering thread and the OpenGL context of the UI thread are shared, this texture can QOpenGLWidgetbe accessed and finally drawn to the main FBO for display.

Scheme process

1. The following is the composition of the three main categories used in the program:

Solution composition

2. The following is the main operating process of the program:

Rendering process

Sample code

head File

#pragma once

#include <QtWidgets>

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 file

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

Guess you like

Origin blog.csdn.net/weixin_41191739/article/details/102392675