若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/94585803
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
目录
前话
相关博客
QGLWidget
概述
QGLWidget子类示例
更新绘制
覆盖层
绘制技术
线程
方案一:在线程中进行缓冲区交换。
方案二:在线程中上载纹理。
方案三:使用QPainer在线程中绘制到QGLWidget中。
QOpenGLWidget
概述
更新绘制
绘制技术
调用OpenGL头文件和函数
代码示例
与QGLWidget的关系
与QGLWidget的不同
多采样
线程
上下文共享
资源初始化和清理
限制(着重介绍了QOpengGLWidget本身透明以及半透明)
选择
开发中模块化时,遇到依赖库使用QGLWidget,也遇到Qt本身库使用QOpenGLWidget的,对两者的区别进行介绍。
《关于 QGLWidget和QOpengGLWidget透明相关问题 的问题》
QGLWidget类是用于呈现OpenGL图形的小部件。
QGLWidget提供了显示集成到Qt应用程序中的OpenGL图形的功能。它很容易使用。继承它并使用子类,就像其他任何QWidget一样,额外的可以选择使用QPainter和标准OpenGL渲染命令。
注意:这个类是传统QtOpenGL模块的一部分,与其他QGL类一样,应该在新的应用程序中避免使用。相反,从Qt5.4开始,Qt推荐使用QOpenGLWidget和QOpenGL类。
QGLWidget提供了三个方便的虚拟函数,可以在子类中重写这些函数来执行典型的OpenGL任务:
继承后,主要是实现三个虚拟函数。
如果需要从paintGL()以外的地方触发重新绘制(典型的例子是使用计时器来动画场景),应该调用小部件的updateGL()函数。
当调用paintGL()、resizeGL()或initializeGL()时,小部件的OpenGL呈现上下文成为当前上下文。如果需要从其他地方调用标准的OpenGL API函数(例如,在小部件的构造函数或自己的paint函数中),则必须首先调用makeCurrent()。
QGLWidget提供了请求新显示格式的功能,您还可以使用自定义的呈现上下文创建小部件。
还可以在QGLwidget对象之间共享OpenGL显示列表(有关详细信息,请参阅QGLwidget构造函数的文档)。
请注意,在Windows下,重新编写QGLWidget时,必须重新创建属于QGLWidget的QGLContext。由于Windows平台的限制,这是必需的。这很可能会给子类化并在QGLwidget上安装了自己的QGLContext的用户带来问题。通过将QGLWidget放在一个虚拟小部件中,然后重新设置虚拟小部件(而不是QGLWidget),可以解决这个问题。这将完全避免这个问题,并且是我们为需要这种功能的用户推荐的。
在MacOS上,当Qt使用Cocoa支持构建时,QGLWidget不能将任何兄弟的widget放在自己的本体上。这是由于可可API的限制,苹果不支持。
如果底层系统支持覆盖,那么QGLWidget除了正常上下文之外还创建一个GL覆盖层上下文。
如果要使用覆盖层,请以格式指定。(注意:必须以传递给QGLWidget构造函数的格式请求覆盖。)GL部件还应实现以下部分或全部虚拟方法:
这些方法与普通paintGL()等函数的工作方式相同,只是当覆盖上下文变为当前时将调用这些方法可以使用makeOverlayCurrent()显式地使覆盖上下文成为当前上下文,并且可以通过调用overlayContext()直接访问覆盖上下文(例如,请求其透明颜色)。
在默认视觉效果位于覆盖平面的X服务器上,非GL Qt窗口也可用于覆盖。
如上所述,子类QGLWidget以下方式呈现纯3D内容:
若要在QGLWidget子类上绘制二维图形,需要重新实现QGLWidget:: paintEvent()并执行以下操作:
从Qt4.8版开始,对线程化GL渲染的支持已经得到了改进。目前支持三种方案:
在双缓冲上下文中交换缓冲区可能是一个同步的锁定调用,在某些GL实现中这可能是一个代价高昂的操作。尤其是嵌入式设备。在GPU进行缓冲区交换时让CPU空转并不是最佳选择。在这些情况下,可以在主线程中进行渲染,并在单独的线程中进行实际的缓冲区交换。这可以通过以下步骤完成:
这样做将释放主线程,以便它可以继续处理UI事件或网络请求。即使涉及到上下文交换,也比在GPU完成交换操作时让主线程等待要好。请注意,这是高度依赖于实现的。
在线程中进行纹理上载对于处理需要显示的大量图像的应用程序(例如照片库应用程序)可能非常有用。这在qt中通过现有的bindTexture() API得到支持。一个简单的方法是创建两个共享的QGLWidget。一个在主GUI线程中是当前的,另一个在纹理上载线程中是当前的。上载线程中的小部件从未显示,它仅用于与主线程共享纹理。对于通过bindTexture()绑定的每个纹理,通知主线程以便它可以开始使用该纹理。
在Qt4.8中,可以使用单独线程中的QPainer绘制到QGLWidget中。请注意,这对于QGLPIxelBuffers和QGLframeBufferObjects也是可能的。由于这仅在GL2绘制引擎中受支持,因此需要OpenGL 2.0或OpenGL ES 2.0。
QGLwidgets只能在主GUI线程中创建。这意味着需要调用doneCurrent()才能从主线程释放GL上下文,然后其他线程才能将小部件拉入其中。然后,需要调用QGLContext::moveToThread()将上下文的所有权转移到要使其成为当前线程的线程。此外,当小部件被调整大小时,或者小部件的一部分暴露出来或者需要重新绘制时,主GUI线程将把调整大小和绘制事件分派给QGLWidget。因此,有必要处理这些事件,因为QGLWidget中的默认实现将尝试使QGLWidget的上下文成为当前上下文,这将再次干扰呈现到小部件中的任何线程。重新实现QGLWidget::paintEvent()和QGLWidget::resizeEvent(),通知渲染线程需要调整大小或更新,注意不要调用基类实现。如果要渲染动画,则可能根本不需要处理绘制事件,因为渲染线程正在进行定期更新。然后,只需重新实现QGLWidget::paintEvent()就可以了。
在进行线程渲染时,一般规则是:请注意,不同线程中的绑定和释放上下文必须由用户同步。GL呈现上下文在任何时候都只能在一个线程中是最新的。如果您试图在QGLwidget上打开一个QPainer,而该Widget的呈现上下文在另一个线程中是最新的,那么它将失败。
除此之外,还支持在单独的线程中使用原始GL调用进行渲染。
QOpenGLWidget类是用于呈现OpenGL图形的小部件。
QOpenGLWidget提供显示集成到Qt应用程序中的OpenGL图形的功能。使用起来非常简单:让类继承它,并像其他QWidget一样使用子类,额外可以选择使用QPainer和标准的OpenGL渲染命令。
QOpenGLWidget提供了三个方便的虚拟函数,可以在子类中重新实现这些函数来执行典型的OpenGL任务:
如果需要从paintGL()以外的地方触发重新绘制(典型的例子是使用计时器来动画场景),您应该调用小部件的update()函数来安排更新。
当调用paintGL()、resizeGL()或initializeGL()时,小部件的OpenGL呈现上下文成为当前上下文。如果需要从其他地方调用标准的OpenGL API函数(例如,在小部件的构造函数或自己的paint函数中),则必须首先调用makeCurrent()。
所有渲染都发生在OpenGL帧缓冲区对象中。makeCurrent()确保它在上下文中绑定。在paintGL()中的呈现代码中创建和绑定其他framebuffer对象时,请记住这一点。永远不要重新绑定ID为0的帧缓冲区。相反,调用defaultFrameBufferObject()获取应该绑定的ID。
当平台支持时,QOpenGLWidget允许使用不同的OpenGL版本和配置文件。只需通过setFormat()设置请求的格式。但是请记住,在同一窗口中拥有多个QOpenGLWidget实例需要它们都使用相同的格式,或者至少使用不使上下文不可共享的格式。要解决此问题,最好使用QSurfaceFormat::setDefaultFormat()而不是setFormat()。
注意:当请求OpenGL核心配置文件上下文时,在某些平台(例如MacOS)上,在构造QApplication实例之前调用QSurfaceFormat::setDefaultFormat()是必需的。这是为了确保上下文之间的资源共享保持功能性,因为所有内部上下文都是使用正确的版本和配置文件创建的。
子类QOpenGLWidget以以下方式呈现纯3D内容:
若要在QGLWidget子类上绘制二维图形,需要重新实现QGLWidget:: paintEvent()并执行以下操作:
仅使用QPainter执行绘图时,也可以像对普通小部件执行一样执行绘图:通过重新实现paintEvent()。
在进行OpenGL函数调用时,强烈建议避免直接调用函数。相反,在面向现代、仅桌面的OpenGL时,更喜欢使用QOpenglFunctions(在制作可移植应用程序时)或版本化的变体(例如,QopenglFunctions_3_2_Core和类似的变体)。这样,应用程序将在所有qt构建配置中正常工作,包括执行动态OpenGL实现加载的配置,这意味着应用程序没有直接链接到GL实现,因此直接函数调用是不可行的。
在paintGL()中,始终可以通过调用QOpenGLContext::currentContext()访问当前上下文。通过调用QOpenGLContex::Functions(),可以从此上下文中检索已初始化、准备好使用的QOpenGLFunctions实例。为每个GL调用添加前缀的另一种方法是从QOpenGLFunctions继承并在InitializeGL()中调用QOpenGLFunctions::nitializeOpenGLFunctions()。
至于OpenGL头文件,请注意,在大多数情况下,不需要直接包含任何头文件,如gl.h。OpenGL相关的qt报头将包括qopengl.h,而qopengl.h又将包含适合系统的报头。这可能是OpenGL ES 3.x或2.0头文件,可用的最高版本,或者是系统提供的gl.h。此外,还为OpenGL和OpenGL ES提供了扩展头文件(在某些系统上称为glext.h)的副本,作为qt的一部分。在可行的情况下,这些将自动包含在平台上。这意味着来自arb、ext、oes扩展的常量和函数指针typedef将自动可用。
最简单的QOpenGLWidget子类可以如下所示:
或者,可以通过从QOpenGLFunctions派生来避免每个OpenGL调用的前缀:
要获取与给定OpenGL版本或配置文件兼容的上下文,或请求深度和模具缓冲区,请调用setFormat():
对于OpenGL 3.0+上下文,当可移植性不重要时,版本化的QopenglFunctions变体可以方便地访问给定版本中可用的所有现代OpenGL函数:
如上所述,全局设置所请求的格式,以便在应用程序的生命周期内应用于所有窗口和上下文,这是一种更简单、更可靠的方法。下面是一个例子:
传统的QtOpenGL模块(以QGL为前缀的类)提供了一个名为QGLWidget的小部件。QOpenGLWidget旨在成为它的现代替代品。因此,特别是在新的应用中,一般建议使用QOpenGLWidget。
虽然API非常相似,但两者之间有一个重要的区别:QOpenGLWidget始终使用帧缓冲区对象在屏幕外进行渲染。另一方面,QGLwidget使用本地窗口和表面。后者在复杂的用户界面中使用时会导致问题,因为根据平台的不同,这些本地子部件可能有各种限制,例如关于堆叠顺序。QOpenGLWidget通过不创建单独的本机窗口来避免这种情况。
由于受到framebuffer对象的支持,QOpenglWidget的行为与QOpenGLWindow非常相似,其更新行为设置为partialUpdateBlit或partialUpdateBlend。这意味着在paintGL()调用之间保留内容,以便可以进行增量呈现。使用QGLWidget(当然,使用默认更新行为的QOpenGLWindow)通常不是这样,因为交换缓冲区会使后台缓冲区保留未定义的内容。
注意:大多数应用程序不需要增量渲染,因为它们将在每次调用绘制时渲染视图中的所有内容。在这种情况下,在paintGL()中尽早调用glclear()是很重要的。这有助于使用基于图块的体系结构的移动GPU识别图块缓冲区不需要使用帧缓冲区的先前内容重新加载。忽略明确的调用可能会导致此类系统的性能显著下降。
注意:避免在QOpenGLWidget上调用winId(),此函数触发本机窗口的创建,从而导致性能降低,并可能出现问题。
除了由framebuffer对象支持的主要概念差异之外,QOpenGLWindow和旧的QGLWidget之间还有许多更小的内部差异:
要启用多采样,请在传递给setFormat()的QSurfaceFormat上设置请求的样本数。在不支持它的系统上,请求可能会被忽略。
多采样支持需要支持多采样渲染器和帧缓冲块。在OpenGLES2.0实现中,可能不存在这些实现。这意味着多重采样将不可用。对于现代OpenGL版本和OpenGLES3.0及更高版本,这通常不再是问题。
在工作线程上执行屏幕外渲染,例如生成纹理,然后在paintGL()中的GUI/main线程中使用纹理,通过公开小部件的QOpenGLContext来支持,以便可以在每个线程上创建与之共享的其他上下文。
通过重新实现paintEvent()什么都不做,可以直接在GUI/主线程外部绘制到QOpenGLWidget的帧缓冲区。必须通过QObject::moveToThread()更改上下文的线程关联。之后,makeCurrent()和doneCurrent()可在工作线程上使用。之后小心地将上下文移回GUI/主线程。
与QGLWidget不同,仅为QOpenGLWidget触发缓冲区交换是不可能的,因为它没有真正的、屏幕上的本机表面。相反,它取决于小部件堆栈来管理GUI线程上的组合和缓冲区交换。当一个线程完成对帧缓冲区的更新后,在GUI/main线程上调用update()来调度合成。
当GUI/主线程执行合成时,必须格外小心以避免使用帧缓冲区。当组合开始和结束时,将发出aboutToCompose()和frameSwapped()的信号。它们在GUI/主线程上发出。这意味着通过使用aboutToCompose()的直接连接可以阻塞GUI/main线程,直到工作线程完成渲染。之后,在发出frameSwapped ()信号之前,工作线程必须不再执行进一步的渲染。如果这是不可接受的,工作线程必须实现双缓冲机制。这涉及到使用另一个渲染目标进行绘制,该目标完全由线程控制,例如,一个额外的framebuffer对象,并在适当的时间点到QOpenGLWidget的framebuffer。
将多个QOpenGLWidget作为子级添加到同一顶级小部件时,它们的上下文将彼此共享。这不适用于属于不同窗口的QOpenGLWidget实例。
这意味着同一窗口中的所有QOpenGLWidget都可以访问彼此的可共享资源,如纹理,并且不需要额外的“全局共享”上下文,QGLWidget就是这样。
要在属于不同窗口的QOpenGLWidget实例之间设置共享,请在实例化Qapplication之前设置qt::aa_shareOpenGLContexts应用程序属性。这将触发所有QOpenGLWidget实例之间的共享,无需任何进一步步骤。
也可以创建额外的QOpenGLContext实例,这些实例与QOpenGLWidget的上下文共享资源(如纹理)。在调用QOpenGLContext::Create()之前,只需将从context()返回的指针传递给QOpenGLContext::SetShareContext()。生成的上下文也可以在不同的线程上使用,允许线程生成纹理和异步纹理上载。
请注意,在底层图形驱动程序方面,QOpenGLWidget需要符合标准的资源共享实现。例如,一些驱动程序,特别是对于移动和嵌入式硬件,在现有上下文和稍后创建的其他上下文之间设置共享时存在问题。当试图利用不同线程之间的共享资源时,其他一些驱动程序的行为可能会出乎意料。
每当调用initializeGL()和paintGL()时,QOpenGLWidget关联的OpenGL上下文都保证是最新的。不要在调用InitializeGL()之前尝试创建OpenGL资源。例如,尝试编译明暗器、初始化顶点缓冲区对象或上载纹理数据将在子类的构造函数中失败。这些操作必须延迟到InitializeGL()。Qt的一些OpenGL帮助程序类(如QOpenGLBuffer或QOpenGLVertexrayObject)具有匹配的延迟行为:它们可以在没有上下文的情况下进行实例化,但所有初始化都将延迟到create()或类似的调用。这意味着它们可以用作QOpenGLWidget子类中的普通(非指针)成员变量,但只能从InitializeGL()调用create()或类似函数。但是要注意并不是所有的类都是这样设计的。如果有疑问,请将成员变量设置为指针,并分别在initializeGL()和析构函数中动态创建和销毁实例。
释放资源还需要上下文是最新的。因此,在继续销毁任何OpenGL资源或包装器之前,执行此类清理的析构函数应该调用makeCurrent()。避免通过deleteLater()或QObject的父级机制延迟删除。无法保证在实际销毁相关实例时正确的上下文是当前的。
因此,在资源初始化和销毁方面,典型的子类通常如下所示:
这当然不是唯一可能的解决方案。另一种方法是使用QOpenGLContext的aboutToBedestroyed()信号。通过使用直接连接将插槽连接到此信号,可以在底层本机上下文句柄或整个QOpenGLContext实例释放时执行清理。以下代码段在原则上等同于前一个代码段:
注意:对于在其生命周期内多次更改其相关顶级窗口的小部件,组合方法是必不可少的。每当小部件或它的父部件被重定位置以使顶级窗口变为不同时,小部件的关联上下文就会被破坏并创建一个新的上下文。然后,调用InitializeGL(),必须重新初始化所有OpenGL资源。因此,执行正确清理的唯一选项是连接到上下文的aboutToBeDestroyed()信号。注意,当信号发出时,所讨论的上下文可能不是当前上下文。因此,在连接的插槽中调用makeCurrent()是一个很好的实践。此外,必须从派生类的析构函数执行相同的清理步骤,因为在销毁小部件时,连接到信号的插槽将不会被调用。
注意:当Qt::AA_ShareOpenGLContexts设置时,小部件的上下文永远不会更改,即使在重新设置时也是如此,因为小部件的关联纹理也保证可以从新的顶级上下文访问。
由于上下文共享,适当的清理尤其重要。即使每个QOpenGLWidget的关联上下文与QOpenGLWidget一起被销毁,该上下文中的可共享资源(如纹理)仍将保持有效,直到QOpenGLWidget所在的顶级窗口被销毁。此外,诸如Qt::AA_ShareOpenGLContexts和一些qt模块之类的设置可能会触发更大范围的共享上下文,从而可能导致在应用程序的整个生命周期内保持存在问题的资源的活动状态。因此,最安全和最健壮的始终是对QOpenGLWidget中使用的所有资源和资源包装器执行显式清理。
将其他小部件放在下面并使QOpenGLWidget透明不会导致预期的结果:下面的小部件将不可见。这是因为实际上,QOpenGLWidget是在所有其他常规、非OpenGL小部件之前绘制的,因此透明解决方案类型不可行。其他类型的布局,如在QOpenGLWidget上具有小部件,将按预期工作。
如果绝对必要,可以通过在QOpenGLWidget上设置Qt::WA_AlwaysStackOnTop属性来克服此限制。但是,请注意,这会破坏堆叠顺序,例如,在QOpenGLWidget上不可能有其他小部件,因此它只能在需要具有其他小部件可见的半透明QOpenGLWidget的情况下使用。
请注意,如果下面没有其他小部件,并且目的是要有一个半透明的窗口,则这不适用。在这种情况下,在顶层窗口上设置Qt::WA_TranslucentBackground的传统方法就足够了。请注意,如果仅在QOpenGLWidget中需要透明区域,则在启用Qt::WA_TranslucentBackground后,需要将Qt::WA_NoSystemBackground返回到false。此外,可能还需要通过setFormat()为QOpenGLWidget的上下文请求alpha通道,具体取决于系统。
QOpenGLWidget支持多个更新行为,就像QOpenGLWindow一样。在保留模式下,前一个paintGL()调用中的渲染内容在下一个调用中可用,允许增量渲染。在非保留模式下,内容将丢失,而paintGL()实现将重新绘制视图中的所有内容。
在qt 5.5之前,QOpenGLWidget的默认行为是在paintGL()调用之间保留呈现的内容。由于Qt5.5不保留默认行为,因为它提供了更好的性能,而且大多数应用程序不需要以前的内容。这也类似于基于OpenGL的QWindow的语义,并且与QOpenGLWindow的默认行为相匹配,因为每个帧的颜色和辅助缓冲区都是无效的。要恢复保留的行为,请使调用SetupDateBehavior()设为partialUpdate。
将QOpenGLWidget添加到窗口中可以为整个窗口启用基于OpenGL的合成。在某些特殊情况下,这可能不理想,需要使用带有独立的本地子窗口的旧qglwidget样式行为。了解此方法限制的桌面应用程序(例如,当涉及重叠、透明、滚动视图和MDI区域时)可以将QOpenGLWindow与QWidget::CreateWindowContainer()一起使用。这是QGLWidget的现代替代品,由于缺少额外的合成步骤,因此比QOpenGLWidget更快。强烈建议将这种方法的使用限制在没有其他选择的情况下。请注意,此选项不适用于大多数嵌入式和移动平台,而且已知某些桌面平台(如MacOS)上也存在问题。稳定的跨平台解决方案始终是QOpenGLWidget。
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/94585803