Qt使用OpenGL进行多线程离屏渲染
基于Qt Widgets的Qt程序,控件的刷新默认状况下都是在UI线程中依次进行的,换言之,各个控件的QWidget::paintEvent方法会在UI线程中串行地被调用。若是某个控件的paintEvent很是耗时(等待数据时间+CPU处理时间+GPU渲染时间),会致使刷新帧率降低,界面的响应速度变慢。
假如这个paintEvent耗时的控件没有使用OpenGL渲染,彻底使用CPU渲染。这种状况处理起来比较简单,只须要另外开一个线程用CPU往QImage里面渲染,当主线程调用到这个控件的paintEvent时,再把渲染好的QImage画出来就能够了,单纯绘制一个QImage仍是很快的。
若是这个paintEvent耗时的控件使用了OpenGL渲染,状况会复杂一些,由于想要把OpenGL渲染过程搬到另一个线程中并非直接把OpenGL调用从UI线程搬到渲染线程就能够的,是须要作一些准备工做的。另外,UI线程如何使用渲染线程的渲染结果也是一个须要思考的问题。
以绘制一个迭代了15次的Sierpinski三角形为例,它总共有3^15=14348907个三角形,在个人MX150显卡上绘制一次需要30ms左右的时间。所以若是我在UI线程渲染这些顶点的话,UI线程的刷新帧率就会掉到30帧左右。如今咱们来看一下如何在另外一个线程中渲染这些三角形。
软硬件环境
CPU:Intel® Core™ i5-8250U CPU @ 1.60GHz多线程
GPU:NVIDIA GeForce MX150(Driver:388.19)svg
OS:Microsoft Windows 10 Home 10.0.18362性能
Compiler:MSVC 2017测试
Optimization flag:O2this
Qt version:5.12.1spa
OpenGL version:4.6.0
概述
有如下主要的类或方法:
- GLWidget
这个类在UI线程中使用,继承了QOpenGLWidget,负责将渲染线程渲染结果绘制到屏幕上。
- Renderer
这个类在渲染线程中使用,负责将三角形渲染到离屏framebuffer中。
- RenderThread
渲染线程管理类,负责初始化渲染线程OpenGL的context。
- TextureBuffer
纹理缓存类,负责将Renderer渲染好的图像缓存到纹理中,供UI线程绘制使用。
- RenderThread::run
渲染线程的例程,负责调用Renderer的方法渲染图像,在Renderer渲染好一帧图像后将图像保存在TextureBuffer中。
context
OpenGL须要context来保存状态,context虽然能够跨线程使用,但没法在多个线程中同时使用,在任意时刻,只能绑定在一个线程中。所以咱们须要为渲染线程建立一个独立的context。
数据共享
UI线程如何访问渲染线程的渲染结果。有两种思路:
- 将渲染结果读进内存,生成QImage,再传给UI线程。这种方式的优势是实现简单。缺点则是性能可能差一些,把显存读进内存是一个开销比较大的操做。
- 将渲染结果保存到纹理中,UI线程绑定纹理绘制到屏幕上。这种方式的优势是性能较方法1好。缺点是为了让两个线程可以共享纹理,须要作一些配置。
在此,咱们选择的是方法2。
初始化渲染线程
了解到上面的这些信息后,咱们来看一下如何初始化渲染线程。
因为须要UI线程可以和渲染线程共享数据,须要调用QOpenGLContext::setShareContext来设置,而这个方法又须要在QOpenGLContext::create方法前调用。UI线程context的QOpenGLContext::create方法调用咱们是没法掌握的,所以须要渲染线程context来调用QOpenGLContext::setShareContext。因为调用时须要确保UI线程context已经初始化,所以在GLWidget::initializeGL中初始化渲染线程比较好,相关代码以下:
void GLWidget::initializeGL()
{initRenderThread();...
}
...
void GLWidget::initRenderThread()
{auto context = QOpenGLContext::currentContext();auto mainSurface = context->surface();auto renderSurface = new QOffscreenSurface(nullptr, this);renderSurface->setFormat(context->format());renderSurface->create();context->doneCurrent();m_thread = new RenderThread(renderSurface, context, this);context->makeCurrent(mainSurface);connect(m_thread, &RenderThread::imageReady, this, [this](){update();}, Qt::QueuedConnection);m_thread->start();
}
...
RenderThread::RenderThread(QSurface *surface, QOpenGLContext *mainContext, QObject *parent): QThread(parent), m_running(true), m_width(100), m_height(100), m_mainContext(mainContext), m_surface(surface)
{m_renderContext = new QOpenGLContext;m_renderContext->setFormat(m_mainContext->format());m_renderContext->setShareContext(m_mainContext);m_renderContext->create();m_renderContext->moveToThread(this);
}
...
在GLWidget::initRenderThread中,咱们首先得到UI线程的context,以及其关联的mainSurface。而后为渲染线程建立了一个QOffscreenSurface,将其格式设置为与UI线程context相同。而后调用doneCurrent取消UI线程context与mainSurface的关联,这是为了可以使UI线程的context和渲染线程的context设置共享关系。待渲染线程初始化完成后,再将UI线程context与mainSurface进行关联。而后设置一个链接用于接收渲染线程的imageReady信号。最后启动渲染线程开始渲染。
在RenderThread::RenderThread中,首先初始化渲染线程的context,因为RenderThread::RenderThread是在UI线程中调用的,还要调用moveToThread将其移到渲染线程中。
渲染线程例程
// called in render thread
void RenderThread::run()
{m_renderContext->makeCurrent(m_surface);TextureBuffer::instance()->createTexture(m_renderContext);Renderer renderer;while (m_running){int width = 0;int height = 0;{QMutexLocker lock(&m_mutex);width = m_width;height = m_height;}renderer.render(width, height);TextureBuffer::instance()->updateTexture(m_renderContext, width, height);emit imageReady();FpsCounter::instance()->frame(FpsCounter::Render);}TextureBuffer::instance()->deleteTexture(m_renderContext);
}
渲染线程开始渲染时,首先绑定context和初始化TextureBuffer。而后在循环中重复执行渲染-保存纹理的循环
离屏渲染
其初始化在Renderer::init中进行,渲染在Renderer::render中进行,各种OpenGL基础教程中都有对离屏渲染的相关介绍和分析,此处再也不赘述。
保存纹理
// called in render thread
void TextureBuffer::updateTexture(QOpenGLContext *context, int width, int height)
{Timer t("ImageBuffer::updateTexture");QMutexLocker lock(&m_mutex);auto f = context->functions();f->glActiveTexture(GL_TEXTURE0);f->glBindTexture(GL_TEXTURE_2D, m_texture);f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);f->glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0);f->glBindTexture(GL_TEXTURE_2D, 0);f->glFinish();
}
在RenderThread::run中调用TextureBuffer::updateTexture将使用glCopyTexImage2D将渲染线程渲染结果保存到纹理中,在Qt中OpenGL调用都须要经过QOpenGLFunction对象,所以将渲染线程的QOpenGLContext对象传进来,能够得到其默认的QOpenGLFunction对象。
因为咱们只使用了一个纹理来缓存图像,若是渲染线程渲染得比较快的话,有些帧就会来不及渲染被丢弃。固然你也能够改程序阻塞渲染线程避免被阻塞。
绘制纹理
void GLWidget::paintGL()
{Timer t("GLWidget::paintGL");glEnable(GL_TEXTURE_2D);m_program->bind();glBindVertexArray(m_vao);if (TextureBuffer::instance()->ready()){TextureBuffer::instance()->drawTexture(QOpenGLContext::currentContext(), sizeof(vertices) / sizeof(float) / 4);}glBindVertexArray(0);m_program->release();glDisable(GL_TEXTURE_2D);FpsCounter::instance()->frame(FpsCounter::Display);
}
...
// called in main thread
void TextureBuffer::drawTexture(QOpenGLContext *context, int vertextCount)
{Timer t("ImageBuffer::drawTexture");QMutexLocker lock(&m_mutex);auto f = context->functions();f->glBindTexture(GL_TEXTURE_2D, m_texture);f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);f->glActiveTexture(GL_TEXTURE0);f->glDrawArrays(GL_TRIANGLES, 0, vertextCount);f->glBindTexture(GL_TEXTURE_2D, 0);//f->glFinish();
}
在GLWidget::paintGL中调用TextureBuffer::drawTexture来绘制缓存的纹理。
性能
上面所作的这一切,可以提升性能吗?很遗憾,答案是“不必定”。就这个demo而言,渲染过程几乎彻底不须要等待数据和CPU处理(除了初始化时须要CPU计算),不断使用GPU进行渲染,这致使GPU占用率几乎达到了100%,成为了一个瓶颈。当主线程进行OpenGL调用时,极可能会由于正在处理渲染线程的OpenGL调用而被阻塞,致使帧率降低。使用NVIDIA Nsights Graphics实测结果以下:


第一幅图是刚打开程序时的帧率,基本稳定在60帧,第二幅图是运行一段时间后的帧率,时常跌到30帧。就平均帧率而言,性能较单线程渲染仍是有提高的。至于为何运行一段时间后帧率会降低,猜测是GPU温度升高被降频致使的,使用GPU-Z观察GPU时钟频率能够验证这一猜测。
若是渲染过程当中等待数据和CPU处理时间占了必定的比重的话,多线程离屏渲染就有优点了。不过在这种状况下,单把等待数据和CPU处理的代码移到独立线程也许是个不错的选择。具体采用哪一种方案仍是要根据实际测试效果来决定。
相关文章:
Qt使用OpenGL进行多线程离屏渲染
基于Qt Widgets的Qt程序,控件的刷新默认状况下都是在UI线程中依次进行的,换言之,各个控件的QWidget::paintEvent方法会在UI线程中串行地被调用。若是某个控件的paintEvent很是耗时(等待数据时间CPU处理时间GPU渲染时间)…...
Vue基础入门讲义(三)-指令
文章目录1.什么是指令?2.插值表达式2.1.花括号2.2.插值闪烁2.3.v-text和v-html3.v-model4.v-on4.1.基本用法4.2.事件修饰5.v-for5.1.遍历数组5.2.数组角标5.3.遍历对象6.key7.v-if和v-show7.1.基本使用7.2.与v-for结合7.3.v-else7.4.v-show8.v-bind8.1. 属性上使用v…...
pod资源限制,探针(健康检查)
pod资源限制,探针(健康检查)一、资源限制当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源当为 Pod 中的容器指定了 request 资源时,调度器就使用…...
Python | 蓝桥杯进阶第一卷——字符串
欢迎交流学习~~ 专栏: 蓝桥杯Python组刷题日寄 蓝桥杯进阶系列: 🏆 Python | 蓝桥杯进阶第一卷——字符串 🔎 Python | 蓝桥杯进阶第二卷——递归(待续) 💝 Python | 蓝桥杯进阶第三卷——动态…...
2023-03-03 mysql列存储-cpu占用100%-追踪思路
摘要: 最近在处理mysql列存储时, 发现在执行explain时, cpu占用达到了100%. 本文分析定位该问题的思路过程 现象: mysqld进程占用100%使用kill processlist终止会话, 无响应查看show processings; 发现一直在运行mysql> show processlist; +----+-----------------+-----…...
JVM—类加载子系统
JVM细节版架构图 本文针对Class Loader SubSystem这一块展开讲解类加载子系统的工作流程 类加载子系统作用 1.类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识即16进制CA FE BA BE; 2.加载后的Class类信息…...
在codeIgniter3中session.php中的数组追加值
如果key是字符串时,输出什么值?会直接把atime()的时间戳添加到key是字符串时,输出什么值?会直接把atime()的时间戳添加到key是字符串时,输出什么值?会直接把atime()的时间戳添加到arr[‘vars’]数组里面&am…...
Windows环境下Gpu版本的Pytorch安装
文章目录安装步骤总览(6步)1 首先看电脑有没有显卡,显卡是否支持cuda软件1.1 先看自己电脑是否有显卡1.2 两种方法看自己的电脑的显卡驱动支持的CUDA1.3 显卡,显卡驱动、CUDA、CUDNN 4者说明2 安装CUDA,就是1个软件2.1 检测自己电…...
项目实战典型案例13——学情页面逻辑问题
学情页面逻辑问题一:背景介绍二:学情页面逻辑问题分析逻辑问题缓存滥用的问题三:LocalStorage基础知识数据结构特性应用场景localStorage常用方法四:总结升华一:背景介绍 本篇博客是对项目开发中出现的学情页面逻辑问…...
工作日志day02
1.云计算? 相关职位 开源软件和linux起源: 自由软件之父:理查德.斯托曼linux之父:林纳斯.本纳第克特.托瓦兹linux发行版 RHEL:Red Hat Enterprise Linux 红帽linux商业公司CentOS:Community Enterprise Operating Sys…...
C++Primer16.1.6节练习
练习16.28: 简易的shared_ptr代码如下 #include <iostream> #include <vector> #include <list> using namespace std;//shared_ptr模板 template<typename T>class SharedPtr {friend SharedPtr<T>& MakeShared(T* t); public…...
初尝并行编程
进程被分为后台进程和应用进程 大部分后台进程在系统开始运行时被操作系统启动,完成操作系统的基础服务功能。大部分应用进程由用户启动,完成用户所需的具体应用功能 进程由程序段、数据段、进程控制块三部分组成 程序段也被称为是代码段,…...
keepalived学习记录:对其vip漂移过程采用gdb跟踪
对其vip漂移过程采用gdb跟踪keepalived工具主要功能产生vip漂移过程两种情况gdb调试常用命令gdb调试时打到的函数栈(供学习参考)函数栈的图是本人理解下画的,不对请多指正 keepalived主要有三个进程,父进程是core进程,…...
51单片机串口通讯原理及程序源码-----day8
51单片机串口通讯原理及程序源码-----day8 1.定义单片机为TTL电平:高 5V 低 0V RS232电平: 计算机的串口高 -12V 低12V 所以计算机与单片机之间通讯时需要加电平转换芯片CH340T 、 MAX232。 2.通信分类: (1)并行通信通…...
mongodb入门到使用(下)
mongodb中常用命令操作一、用户操作二、创建用户三、数据库操作基本操作四、扩展操作五、集合操作一、用户操作 在mongo中使用mongodb都需要在admin数据库中操作。然后在使用下面的命令 use admin二、创建用户 db.createUser({"user":"imooc", #用户名&q…...
云HIS系统源码 医院his源码 云his源码
大型医院his系统源码 SaaS运维平台多医院入驻强大的电子病历完整文档 ,有演示 一、系统概述: 基层卫生健康云是一款满足基层医疗机构各类业务需要的健康云产品。该产品能帮助基层医疗机构完成日常各类业务,提供病患挂号支持、病患问诊、电子…...
朴素贝叶斯法学习笔记
频率派和贝叶斯派 频率派认为可以通过大量实验,从样本推断总体。比如假定总体服从均值为μ\muμ,方差为σ\sigmaσ的分布。根据中心极限定理,是可以通过抽样估算总体的参数的,而且抽样次数越多,对总体的估计就越准确。…...
vscode与C++安装与使用【不好用来骂我】
网上教程很多,但是都不太好用,这是我垃圾堆里淘金淘出来的教程: 安装软件 安装 Visual Studio Code: 你需要下载并安装 Visual Studio Code,可以在官网下载 https://code.visualstudio.com/download。 安装 C 扩展: 在 Visual S…...
C++11使用多线程(线程池)计算相似度实现性能优化
需求:图像识别中,注册的样本多了会影响计算速度,成为性能瓶颈,其中一个优化方法就是使用多线程。例如,注册了了3000个特征,每个特征4096个float。可以把3000个特征比对放到4个线程中进行计算,然…...
【测绘程序设计】——平面坐标转换
测绘工程中经常遇到平面坐标转换——比如,北京54(或西安80)平面坐标转换成CGCS2000平面坐标、工程独立坐标系平面坐标转换成CGCS2000平面坐标等,常用转换模型包括:①三参数法(2平移+1旋转);②四参数法(赫尔默特法,2平移+1旋转+1尺度);③六参数法(仿射变换法,2平移…...
基于MATLAB的隔离型DC DC变换器系统设计:技术指标明确、包含设计报告与仿真程序的全过程解析
基于MATLAB的单端反激——隔离型DC/DC变换器系统设计 本设计包括设计报告,仿真程序。技术指标 输入电压、输出电压、输出功率、纹波系数、开关频率见下图凌晨三点盯着示波器的我,突然被显示器上的锯齿状波形逗笑了——这哪儿是DC/DC变换器啊,…...
深入解析IoU(Jaccard系数)在目标检测中的关键作用与高效实现
1. IoU究竟是什么?从基础概念到视觉理解 第一次接触目标检测时,我对着论文里满屏的"IoU"缩写发懵——这到底是个什么魔法指标?后来在调试YOLO模型时才发现,这个看似简单的比值,实际上是整个检测任务的基石性…...
微信小程序UI组件库终极指南:WeUI-WXSS与Vant、ColorUI深度对比分析
微信小程序UI组件库终极指南:WeUI-WXSS与Vant、ColorUI深度对比分析 【免费下载链接】weui-wxss A UI library by WeChat official design team, includes the most useful widgets/modules. 项目地址: https://gitcode.com/gh_mirrors/we/weui-wxss WeUI-WX…...
Swin2SR进阶使用:通过HTTP链接实现远程增强
Swin2SR进阶使用:通过HTTP链接实现远程增强 1. 引言:从本地工具到远程服务 如果你用过Swin2SR这个AI图像超分工具,一定会被它“化腐朽为神奇”的能力震撼——一张模糊的小图,经过AI的“脑补”,瞬间变成细节丰富的高清…...
相场法在水力压裂仿真中越来越火,它能用连续函数描述裂缝边界,比传统方法更适合处理复杂裂缝网络。今天咱们拿COMSOL 6.0开刀,看看四个实战模型的实现门道
相场法水力压裂,共四个模型,comsol6.0版本及以上,附赠参考文献 模型一:对称三簇压裂;模型二:水力裂缝与天然裂缝相交;模型三:单水平裂缝扩展;模型四:水平裂缝…...
3个AI工具如何提升动态图像质量?专业级画质增强全攻略
3个AI工具如何提升动态图像质量?专业级画质增强全攻略 【免费下载链接】video2x A lossless video/GIF/image upscaler achieved with waifu2x, Anime4K, SRMD and RealSR. Started in Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/…...
零基础玩转Mermaid在线编辑器:30分钟从入门到精通专业图表制作
零基础玩转Mermaid在线编辑器:30分钟从入门到精通专业图表制作 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/GitHub_Trending/me/mermaid-liv…...
Petalinux实战:从零构建嵌入式Linux系统的boot与kernel镜像
1. Petalinux环境配置与工程创建 第一次接触Petalinux时,我被它强大的功能震撼到了。这个由Xilinx推出的嵌入式Linux开发工具链,能够帮我们快速构建从bootloader到kernel的完整系统镜像。不过在实际操作中,我发现环境配置这一步特别关键&…...
AIGlasses_for_navigation视频处理应用:使用AE制作导航效果演示片段视频
AIGlasses_for_navigation视频处理应用:使用AE制作导航效果演示片段视频 你有没有想过,那些看起来科技感十足、路径光效流畅的AR导航演示视频是怎么做出来的?是不是觉得需要专业的动画团队才能实现? 其实,借助像Afte…...
C++的std--ranges适配器视图与惰性求值在无限序列中的潜在应用
C的std::ranges适配器视图与惰性求值在无限序列中的潜在应用 现代C通过引入std::ranges库,为序列操作带来了更简洁、高效的编程范式。其中,适配器视图与惰性求值的结合,为处理无限序列提供了全新的可能性。这种技术不仅能够避免不必要的计算…...
