我们知道,QOpenGLWidget
是Qt中继承自QWidget的一个类,对OpenGL的渲染环境进行了很好的封装。用户在使用时只用继承并重写几个关键的虚函数即可完成OpenGL资源的准备和每帧画面的绘制。
但是,也正是因为QOpenGLWidget
是继承自QWidget的一个类,也决定了它的绘制函数是在UI线程调用的,也就是说,当我们重写了QOpenGLWidget
的paintGL
函数时,该函数是在UI线程被调用的。那么如果我们在paintGL
函数里花太多时间进行场景绘制的话,会直接导致UI线程的更新不及时,鼠标键盘交互事件被阻塞,造成界面卡死的现象。
所以当我们选择QOpenGLWidget
来在Qt界面中进行OpenGL场景绘制时,我们需要一种更高效的方式来保证场景能够正常渲染,且不会造成UI线程的阻塞。通常情况下,我们的可以选择的方式是在QOpenGLWidget
中应用多线程渲染。
下面,我来介绍一种在我们产品中被使用,证明有效且高效的一种QOpenGLWidget
多线程渲染处理方式。这套方案针对的应用场景是当我们的OpenGL场景需要按照一定的帧速率定时进行更新,且OpenGL场景的绘制需要消耗相当长的时间,可能超过16甚至33毫秒时。在这种场景下,OpenGL的渲染肯定会阻塞UI线程,造成画面卡点,交互卡顿。我们多线程OpenGL渲染的核心流程是这样的
首先,我们需要一个Renderer对象,专门负责核心的OpenGL场景渲染逻辑,并且我们需要一个Render Thread来和Renderer进行绑定,作为核心OpenGL场景的渲染线程。
其次,我们需要为Renderer创建一个和QOpenGLWidget
的QOpenGLContext
共享的新QOpenGLContext
对象,用于在Render Thread中作为渲染所使用的上下文。
我们在Renderer进行渲染时, 需要将场景的结果渲染到一个OpenGL纹理上,而不是直接绘制到Render Buffer上。由于渲染线程的上下文和UI线程的OpenGL上下文共享,这个纹理可以在QOpenGLWidget
被访问,并最终绘制到主FBO上用于显示。
头文件
#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();
}
}