QOpenGLWidgetマルチスレッドレンダリングについて
背景紹介
QOpenGLWidget
これは、OpenGLレンダリング環境をカプセル化するQtのQWidgetから継承されたクラスであることがわかっています。ユーザーは、OpenGLリソースの準備と各フレームの描画を完了するために、いくつかの主要な仮想関数を継承して書き直すだけで済みます。
ただし、QOpenGLWidget
クラスがQWidgetを継承するためですが、その関数がUIスレッドで描画されること、つまりUIスレッドで呼び出されるtime関数を書き直しQOpenGLWidget
たpaintGL
ときにも決定されます。そのpaintGL
ため、シーンを描画するために関数に多くの時間を費やすと、UIスレッドが時間内に直接更新されず、マウスとキーボードの対話イベントがブロックされ、インターフェイスがスタックします。
したがってQOpenGLWidget
、QtインターフェイスでOpenGLシーンを描画することを選択した場合、UIスレッドをブロックせずにシーンを正常にレンダリングできるようにするためのより効率的な方法が必要です。通常の状況ではQOpenGLWidget
、でマルチスレッドレンダリングを適用することを選択します。
はじめに
次に、当社の製品で使用され、効果的かつ効率的であることが証明されているQOpenGLWidget
マルチスレッドレンダリング処理方法を紹介します。このソリューションの対象となるアプリケーションシナリオは、OpenGLシーンを特定のフレームレートで定期的に更新する必要があり、OpenGLシーンの描画に16ミリ秒または33ミリ秒を超える長い時間がかかる場合です。このシナリオでは、OpenGLレンダリングはUIスレッドを確実にブロックし、画面のフリーズとインタラクティブなフリーズを引き起こします。マルチスレッドOpenGLレンダリングのコアプロセスは
次のようになります。最初に、コアOpenGLシーンレンダリングロジックを担当するRendererオブジェクトが必要です。また、レンダリングスレッドとしてRendererにバインドするRenderスレッドが必要です。コアOpenGLシーン。
第二に、我々は、レンダラと作成する必要が新しい共有でレンダリングスレッドで使用されるレンダリングするためのコンテキストとしてオブジェクトを。レンダラーでレンダリングするときは、シーンの結果をレンダーバッファーに直接描画するのではなく、OpenGLテクスチャにレンダリングする必要があります。レンダリングスレッドのコンテキストとUIスレッドのOpenGLコンテキストが共有されているため、このテクスチャにアクセスして、最終的にメインFBOに描画して表示することができます。QOpenGLWidget
QOpenGLContext
QOpenGLContext
QOpenGLWidget
スキームプロセス
1.以下は、プログラムで使用される3つの主要なカテゴリの構成です。
2.プログラムの主な操作プロセスは次のとおりです。
サンプルコード
ヘッドファイル
#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ファイル
#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();
}
}