• 【OpenGL概念】QOpenGLWidget类详述--此文档基于Qt5.14.2


    一、说明

       QOpenGLWidget提供了在Qt应用程序中显示集成OpenGL图形的功能。使用它非常简单:让您的类继承它,并像使用任何其他QWidget一样使用子类,除了您可以选择使用QPainter和标准OpenGL渲染命令之间的区别。

    二、QOpenGLWidget概述

       QOpenGLWidget提供了三个方便的虚拟函数,您可以在子类中重新实现以执行典型的OpenGL任务:

    •    paintGL() - 渲染OpenGL场景。每当窗口部件需要更新时调用。
    •    resizeGL() - 设置OpenGL视口、投影等。每当窗口部件被调整大小时调用(也在第一次显示时调用,因为所有新创建的窗口部件都自动获得调整大小事件)。
    •    initializeGL() - 设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
      如果您需要从paintGL()之外的其他位置触发重绘(典型的示例是在使用计时器来动画场景时),您应该调用窗口部件的update()函数以安排更新。

       当调用paintGL()、resizeGL()或initializeGL()时,您的窗口部件的OpenGL渲染上下文将变为当前上下文。如果您需要从其他位置调用标准的OpenGL API函数(例如在窗口部件的构造函数或自己的绘图函数中),您必须首先调用makeCurrent()。

       所有渲染都发生在OpenGL帧缓冲对象中。makeCurrent()确保在上下文中绑定它。在paintGL()中的渲染代码中创建和绑定其他帧缓冲对象时请记住这一点。永远不要重新绑定ID为0的帧缓冲。相反,请调用defaultFramebufferObject()以获取应绑定的ID。

    当平台支持时,QOpenGLWidget允许使用不同的OpenGL版本和配置文件。只需通过setFormat()设置请求的格式即可。但是请记住,在同一窗口中具有多个QOpenGLWidget实例需要它们都使用相同的格式,或者至少使用不使上下文不可共享的格式。为了克服这个问题,最好使用QSurfaceFormat::setDefaultFormat()而不是setFormat()。

       注意:在某些平台(例如macOS)请求OpenGL核心配置文件上下文时,在构造QApplication实例之前调用QSurfaceFormat::setDefaultFormat()是必需的。这是为了确保所有内部上下文都使用正确的版本和配置文件创建,以保持上下文之间的资源共享可用。

    三、Painting Techniques 绘制技巧

       根据上述描述,通过创建 QOpenGLWidget 的子类以以下方式呈现纯 3D 内容:

       重新实现 initializeGL() 和 resizeGL() 函数以设置 OpenGL 状态并提供透视变换。
       重新实现 paintGL() 以绘制 3D 场景,仅调用 OpenGL 函数。
       还可以使用 QPainter 在 QOpenGLWidget 子类上绘制 2D 图形:

       在 paintGL() 中,不使用 OpenGL 命令,而是构建一个用于widget的 QPainter 对象。

    3.1 使用 QPainter 的成员函数绘制图元。

       仍然可以直接发出 OpenGL 命令。但是,您必须确保这些命令被 beginNativePainting() 和 endNativePainting() 调用包围。
    当仅使用 QPainter 进行绘图时,也可以通过重新实现paintEvent()像普通widget一样执行绘图:

    3.2 重新实现 paintEvent()函数。

       构造一个 QPainter 对象,以widget为目标。将widget传递给构造函数或 QPainter::begin() 函数。
    使用 QPainter 的成员函数绘制图元。
    当 QPainter 实例被销毁时,绘制完成。或者,显式调用 QPainter::end()。

    3.3 OpenGL Function Calls, Headers and QOpenGLFunctions

       在进行OpenGL函数调用时,强烈建议避免直接调用函数。相反,应该优先使用QOpenGLFunctions(用于制作可移植应用程序),或者版本化变量(例如,当针对现代的仅限桌面的OpenGL时,使用QOpenGLFunctions_3_2_Core等)。这样,应用程序将在所有Qt构建配置中正常工作,包括执行动态OpenGL实现加载的配置。这意味着应用程序不会直接链接到GL实现,因此直接函数调用是不可行的。

       在paintGL()中,当前上下文始终可以通过调用QOpenGLContext::currentContext()进行访问。从这个上下文中,可以通过调用QOpenGLContext::functions()获取已经初始化并准备好使用的QOpenGLFunctions实例。另一种替代方法是继承QOpenGLFunctions并在initializeGL()中调用QOpenGLFunctions::initializeOpenGLFunctions()。

       至于OpenGL头文件,在大多数情况下,通常不需要直接包含任何头文件,例如GL.h。与OpenGL相关的Qt头文件将包括qopengl.h,该文件将自动包含适合该系统的适当头文件。这可能是OpenGL ES 3.x或2.0头文件、可用的最高版本,或系统提供的gl.h。此外,Qt还提供了扩展头文件的副本(在某些系统上称为glext.h),用于OpenGL和OpenGL ES。在可行的平台上,这些扩展将自动可用。这意味着来自ARB、EXT、OES扩展的常量和函数指针typedef自动可用。

    四、Code 示例

    首先,最简单的QOpenGLWidget子类如下所示:

    复制代码

      class MyGLWidget : public QOpenGLWidget
      {
      public:
          MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { }
    
      protected:
          void initializeGL() override
          {
              // Set up the rendering context, load shaders and other resources, etc.:
              QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
              f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
              ...
          }
    
          void resizeGL(int w, int h) override
          {
              // Update projection matrix and other size related settings:
              m_projection.setToIdentity();
              m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
              ...
          }
    
          void paintGL() override
          {
              // Draw the scene:
              QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
              f->glClear(GL_COLOR_BUFFER_BIT);
              ...
          }
    
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    或者,通过从QOpenGLFunctions派生,可以避免每个OpenGL调用的前缀:

      class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
      {
          ...
          void initializeGL() override
          {
              initializeOpenGLFunctions();
              glClearColor(...);
              ...
          }
          ...
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    要获得与给定 OpenGL 版本或配置文件兼容的上下文,或请求深度和模板缓冲区,请调用 setFormat():

      QOpenGLWidget *widget = new QOpenGLWidget(parent);
      QSurfaceFormat format;
      format.setDepthBufferSize(24);
      format.setStencilBufferSize(8);
      format.setVersion(3, 2);
      format.setProfile(QSurfaceFormat::CoreProfile);
      widget->setFormat(format); // must be called before the widget or its parent window gets shown
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

       对于 OpenGL 3.0+ 上下文,当可移植性不重要时,版本化的 QOpenGLFunctions 变体可以轻松访问给定版本中可用的所有现代 OpenGL 函数:

    void paintGL() override
    {
         QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>();
         ...
         f->glDrawArraysInstanced(...);
         ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

       如上所述,全局设置请求的格式使其在应用程序的生命周期内适用于所有窗口和上下文更简单、更可靠。 下面是一个例子:

      int main(int argc, char **argv)
      {
          QApplication app(argc, argv);
    
          QSurfaceFormat format;
          format.setDepthBufferSize(24);
          format.setStencilBufferSize(8);
          format.setVersion(3, 2);
          format.setProfile(QSurfaceFormat::CoreProfile);
          QSurfaceFormat::setDefaultFormat(format);
    
          MyWidget widget;
          widget.show();
    
          return app.exec();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    五、Relation to QGLWidget 和QGLWidget的关系

       QtOpenGL模块(以QGL为前缀的类)提供了一个名为QGLWidget的widget。 QOpenGLWidget旨在成为其现代替代品。因此,在新的应用程序中,一般建议使用QOpenGLWidget。

       虽然API非常相似,但两者之间存在一个重要区别:QOpenGLWidget始终使用帧缓冲区对象进行离屏渲染,而QGLWidget则使用本地窗口和表面进行渲染。后者在使用复杂用户界面时会出现问题,因为根据不同的平台,此类本地子widget可能具有各种限制,例如关于堆叠顺序的限制。 QOpenGLWidget通过不创建单独的本地窗口来避免这种问题。

       由于有帧缓冲区对象的支持,QOpenGLWidget的行为与将更新行为设置为PartialUpdateBlit或PartialUpdateBlend的QOpenGLWindow非常相似。这意味着在paintGL()调用之间保留内容,以便可以进行增量渲染。使用QGLWidget(自然地,使用默认更新行为的QOpenGLWindow通常不是这种情况)通常不是这种情况,因为交换缓冲区会使后备缓冲区的内容未定义。

       注意:大多数应用程序不需要增量渲染,因为它们会在每个paint调用中渲染视图中的所有内容。在这种情况下,重要的是尽早在paintGL()中调用glClear()。这有助于使用tile-based的体系结构的移动GPU,以识别tile缓冲区不需要重新加载帧缓冲区的先前内容。省略清除调用可能导致这些系统的显着性能下降。

       注意:避免在QOpenGLWidget上调用winId()。这个函数会触发本地窗口的创建,导致性能降低和可能的渲染故障。

    六、Differences to QGLWidget 和QGLWidget的不同

       除了由帧缓冲对象支持的概念差异之外,QOpenGLWidget和较旧的QGLWidget之间存在一些较小的内部差异:在调用paintGL()时,OpenGL状态的设置。 QOpenGLWidget通过glViewport()设置视口。它不执行任何清除操作。
       在使用QPainter开始绘制时的清除操作。与普通widget不同,QGLWidget默认为autoFillBackground设置为true。然后每次使用QPainter :: begin()时,它都会执行清除以匹配调色板的背景颜色。 QOpenGLWidget不遵循此行为:autoFillBackground默认为false,就像对于任何其他widget一样。唯一的例外是当作为其他widget(例如QGraphicsView)的视口时。在这种情况下,autoFillBackground将自动设置为true,以确保与基于QGLWidget的视口兼容。

    6.1 Multisampling 多重采样

       为启用多重采样,请在传递给setFormat()的QSurfaceFormat上设置所需采样数。在不支持它的系统上,该请求可能会被忽略。

       多重采样支持需要支持多重采样渲染缓冲区和帧缓冲区混合的支持。在OpenGL ES 2.0实现中,这些可能不存在。这意味着多重采样将不可用。对于现代OpenGL版本和OpenGL ES 3.0及更高版本,这通常不再是问题。

    6.2 Threading 多线程

       在工作线程上执行离屏渲染,例如生成纹理,然后在GUI/主线程中使用它们进行paintGL()绘制,可以通过公开widget的QOpenGLContext来支持,在每个线程上可以创建与其共享的附加上下文。

       在GUI/主线程之外直接绘制到QOpenGLWidget的帧缓冲区是可能的,可以通过重新实现paintEvent()为空来实现。必须通过QObject::moveToThread()更改上下文的线程关联性。之后,makeCurrent()和doneCurrent()可用于工作线程。在此之后,请务必将上下文移回GUI/主线程。

       与QGLWidget不同,仅为QOpenGLWidget触发缓冲区交换是不可能的,因为它没有真实的屏幕原生表面。相反,由widget堆栈管理在GUI线程上的组合和缓冲区交换。当线程完成帧缓冲更新时,在GUI/主线程上调用update()以安排组合。

       必须特别注意,在GUI/主线程执行合成时避免使用帧缓冲区。aboutToCompose()和frameSwapped()信号将在开始和结束组合时发出。它们在GUI/主线程上发出。这意味着,通过使用直接连接,aboutToCompose()可以阻塞GUI/主线程,直到工作线程完成渲染为止。之后,工作线程必须不再执行进一步的渲染,直到发出frameSwapped()信号。如果这不可接受,则工作线程必须实现双缓冲机制。这涉及使用替代渲染目标进行绘制,该目标完全由线程控制,例如,使用额外的帧缓冲对象,并在适当的时间将其复制到QOpenGLWidget的帧缓冲区。

    6.3 Context Sharing 上下文共享

       当将多个QOpenGLWidget作为子项添加到同一顶层widget中时,它们的上下文将共享。这不适用于属于不同窗口的QOpenGLWidget实例。

       这意味着同一窗口中的所有QOpenGLWidget可以访问彼此共享的资源,例如纹理,而不需要额外的“全局共享”上下文,就像QGLWidget的情况一样。

       要在属于不同窗口的QOpenGLWidget实例之间设置共享,可以在实例化QApplication之前设置Qt::AA_ShareOpenGLContexts应用程序属性。这将触发所有QOpenGLWidget实例之间的共享,无需进一步操作。

       创建额外的QOpenGLContext实例,共享与QOpenGLWidget上下文的资源(例如纹理)也是可能的。只需在调用QOpenGLContext::create()之前将context()返回的指针传递给QOpenGLContext::setShareContext()。结果的上下文也可以在不同的线程上使用,允许纹理的线程生成和异步纹理上传。

       请注意,当涉及到底层图形驱动程序时,QOpenGLWidget 期望资源共享的标准一致性实现。例如,一些驱动程序(特别是移动和嵌入式硬件的驱动程序)在设置现有上下文和稍后创建的其他上下文之间的共享方面存在问题。在尝试在不同的线程之间利用共享资源时,其他某些驱动程序可能会表现出意外的行为。

    七、Resource Initialization and Cleanup 资源的初始化和清除

       QOpenGLWidget的相关OpenGL上下文在调用initializeGL()和paintGL()时保证处于当前状态。不要在子类的构造函数中尝试创建OpenGL资源。例如,试图编译着色器、初始化顶点缓冲对象或上传纹理数据将会失败。这些操作必须推迟到initializeGL()中进行。Qt的一些OpenGL辅助类,如QOpenGLBuffer或QOpenGLVertexArrayObject,具有匹配的延迟行为:它们可以在没有上下文的情况下实例化,但所有初始化都推迟到调用create()或类似函数时进行。这意味着它们可以作为QOpenGLWidget子类中的普通(非指针)成员变量使用,但create()或类似函数只能从initializeGL()中调用。然而要注意,并非所有类都是这样设计的。当存在疑问时,将成员变量设为指针,并在initializeGL()和析构函数中动态创建和销毁实例。

       释放资源也需要上下文处于当前状态。因此,执行此类清理工作的析构函数在销毁任何OpenGL资源或包装器之前应调用makeCurrent()。避免使用deleteLater()或QObject的父子关系机制进行延迟删除。不能保证在实例实际销毁时正确的上下文处于当前状态。

       因此,典型的子类在资源初始化和销毁方面通常如以下代码所示:

     class MyGLWidget : public QOpenGLWidget
      {
          ...
    
      private:
          QOpenGLVertexArrayObject m_vao;
          QOpenGLBuffer m_vbo;
          QOpenGLShaderProgram *m_program;
          QOpenGLShader *m_shader;
          QOpenGLTexture *m_texture;
      };
    
    
      MyGLWidget::MyGLWidget()
          : m_program(0), m_shader(0), m_texture(0)
      {
          // No OpenGL resource initialization is done here.
      }
    
      MyGLWidget::~MyGLWidget()
      {
          // Make sure the context is current and then explicitly
          // destroy all underlying OpenGL resources.
          makeCurrent();
    
          delete m_texture;
          delete m_shader;
          delete m_program;
    
          m_vbo.destroy();
          m_vao.destroy();
    
          doneCurrent();
      }
    
      void MyGLWidget::initializeGL()
      {
          m_vao.create();
          if (m_vao.isCreated())
              m_vao.bind();
          m_vbo.create();
          m_vbo.bind();
          m_vbo.allocate(...);
          m_texture = new QOpenGLTexture(QImage(...));
          m_shader = new QOpenGLShader(...);
          m_program = new QOpenGLShaderProgram(...);
    
          ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

       这自然不是唯一可能的解决方案。另一种选择是使用QOpenGLContext的aboutToBeDestroyed()信号。通过将一个使用直接连接的槽连接到该信号,可以在底层native上下文句柄或整个QOpenGLContext实例将要释放时执行清理。以下代码片段原则上等同于先前的代码片段:

      void MyGLWidget::initializeGL()
      {
          // context() and QOpenGLContext::currentContext() are equivalent when called from initializeGL or paintGL.
          connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &MyGLWidget::cleanup);
      }
    
      void MyGLWidget::cleanup()
      {
          makeCurrent();
          delete m_texture;
          m_texture = 0;
          ...
          doneCurrent();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

       注意:对于在其生命周期内多次更改关联顶级窗口的widget,需要采用综合方法。每当widget或其父级被重新父化以使顶级窗口不同,widget的相关上下文都将被销毁,并创建一个新的上下文。然后调用initializeGL(),在其中必须重新初始化所有OpenGL资源。由于这样做才能进行正确的清理,因此唯一的选项是连接到上下文的aboutToBeDestroyed()信号。请注意,发出信号时可能不是当前上下文。因此,在连接的槽中调用makeCurrent()是个好习惯。此外,派生类的析构函数也必须执行相同的清理步骤,因为与信号连接的槽在widget被销毁时不会被调用。

       注意:当设置Qt::AA_ShareOpenGLContexts时,widget的上下文永远不会改变,即使重新父化,因为widget的相关纹理也可以从新顶级窗口的上下文中访问。

       由于上下文共享,正确的清理尤为重要。尽管每个QOpenGLWidget的相关上下文都会随着QOpenGLWidget一起被销毁,但该上下文中的可共享资源(如纹理)将保持有效,直到包含QOpenGLWidget的顶级窗口被销毁。此外,像Qt::AA_ShareOpenGLContexts和一些Qt模块之类的设置可能会触发更广泛的上下文共享范围,可能会导致保持相关资源在整个应用程序生命周期中处于活动状态。因此,最安全和最可靠的做法始终是对QOpenGLWidget中使用的所有资源和资源包装器执行显式清理。

    八、Limitations 限制

       将其他widget放在QOpenGLWidget下面并使其透明不会产生预期的结果:在QOpenGLWidget下面的widget将不可见。这是因为实际上,QOpenGLWidget在所有常规的非OpenGL的widget之前绘制,因此透明的解决方案不可行。其他类型的布局,例如将widget放在QOpenGLWidget上方,将如预期那样运作。

       在绝对必要时,可以通过在QOpenGLWidget上设置Qt::WA_AlwaysStackOnTop属性来克服此限制。但是请注意,这会破坏堆栈顺序,例如无法将其他widget置于QOpenGLWidget之上,因此仅应在需要在其他widget可见的情况下使用半透明的QOpenGLWidget的情况下使用。

       请注意,当没有其他widget在下面并且意图是创建一个半透明窗口时,这不适用。在这种情况下,设置Qt::WA_TranslucentBackground的传统方法就足够了。请注意,如果只希望在QOpenGLWidget中具有透明区域,则需要在启用Qt::WA_TranslucentBackground后将Qt::WA_NoSystemBackground重新设置为false。此外,根据系统,可能还需要通过setFormat()请求QOpenGLWidget的上下文的alpha通道。

       QOpenGLWidget支持多种更新行为,就像QOpenGLWindow一样。在保留模式下,上一次paintGL()调用中呈现的内容在下一次调用中可用,从而允许增量渲染。在非保留模式下,内容将丢失,并且期望paintGL()实现重新绘制视图中的所有内容。

       在Qt 5.5之前,QOpenGLWidget的默认行为是保留paintGL()调用之间的呈现内容。自Qt 5.5以来,默认行为是非保留模式,因为这提供了更好的性能,并且大多数应用程序不需要先前的内容。这也类似于基于OpenGL的QWindow的语义,并匹配QOpenGLWindow的默认行为,即每个帧的颜色和辅助缓冲区无效。要恢复保留的行为,请使用PartialUpdate调用setUpdateBehavior()。

    九 方法和替代(Alternatives)

    9.1 限定和说明

       将 QOpenGLWidget 添加到窗口中会为整个窗口打开基于 OpenGL 的合成。在某些特殊情况下,这可能不是理想的选择,并且需要一个单独的本地子窗口,类似于 QGLWidget 的行为。了解这种方法的限制(例如重叠、透明度、滚动视图和 MDI 区域等),桌面应用程序可以使用 QOpenGLWindow 与 QWidget::createWindowContainer()。这是 QGLWidget 的现代替代品,由于缺少额外的合成步骤,速度比 QOpenGLWidget 快。强烈建议仅在没有其他选择的情况下使用此方法。请注意,此选项不适用于大多数嵌入式和移动平台,并且在某些桌面平台上(例如 macOS)也存在问题。稳定、跨平台的解决方案始终是 QOpenGLWidget。

       OpenGL 是 Silicon Graphics, Inc. 在美国和其他国家的商标。 另请参见 QOpenGLFunctions、QOpenGLWindow、Qt::AA_ShareOpenGLContexts 和 UpdateBehavior。

    Member Type Documentation enum QOpenGLWidget::UpdateBehavior
    Constant Value Descirption

       QOpenGLWidget::NoPartialUpdate 0 QOpenGLWidget 将在渲染到屏幕后丢弃颜色缓冲区和辅助缓冲区的内容。这与使用默认opengl启用的QWindow作为参数调用QOpenGLContext::swapBuffers的行为相同。当使用帧缓冲对象作为渲染目标时,NoPartialUpdate 可以在某些在移动和嵌入式领域中常见的硬件架构上带来一些性能优势。帧缓冲对象会在帧之间被无效化,如果支持的话会使用glDiscardFramebufferEXT或glClear。有关更多信息,请参见 EXT_discard_framebuffer 文档:https://www.khronos.org/registry/gles/extensions/EXT/EXT_discard_framebuffer.txt。
       QOpenGLWidget::PartialUpdate 1 帧缓冲对象的颜色缓冲区和辅助缓冲区在帧之间不会被无效化。

    9.2 函数后信号发起

    9.2.1 构造函数

    Member Function Documentation
    QOpenGLWidget::QOpenGLWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags())
    
    • 1
    • 2

       构造一个widget,它是 parent 的子部件,并将widget标志设置为 f。

    9.2.2 顶层窗口整合部件

    [signal] void QOpenGLWidget::aboutToCompose()
    
    • 1

       当widget所在的顶层窗口即将开始组合其 QOpenGLWidget 子部件和其他widget的纹理时,将发出此信号。

    9.2.3 窗口重建

    [signal] void QOpenGLWidget::aboutToResize()
    
    • 1

       当widget的大小改变并且帧缓冲对象即将被重新创建时,将发出此信号。

    9.2.4 顶层窗口整合部件完成

    [signal] void QOpenGLWidget::frameSwapped()
    
    • 1

       该信号在小部件的顶级窗口完成组合后发出,并从其可能阻塞的 QOpenGLContext::swapBuffers() 调用返回。

       当widget所在的顶层窗口完成合成并从其可能阻塞的 QOpenGLContext::swapBuffers() 调用返回后,将发出此信号。

    9.2.5 窗口尺寸变化

    [signal] void QOpenGLWidget::resized()
    
    • 1

       当widget的大小改变并且帧缓冲对象因此被重新创建后,将立即发出此信号。

    9.3 可以超载的函数

    9.3.1 析构函数

    [virtual] QOpenGLWidget::~QOpenGLWidget()
    
    • 1

       销毁 QOpenGLWidget 实例,释放其资源。QOpenGLWidget 的上下文在析构函数中被设置为当前上下文,从而安全地释放任何子对象可能需要释放的属于该widget提供的 OpenGL 资源。

       警告:如果您有包装 OpenGL 资源(例如 QOpenGLBuffer、QOpenGLShaderProgram 等)作为 OpenGLWidget 子类成员的对象,则可能需要在该子类的析构函数中添加对 makeCurrent() 的调用。由于 C++ 对象销毁规则,在调用此函数之前(但在子类析构函数运行后)这些对象将被销毁,因此在此函数中使 OpenGL 上下文成为当前上下文太晚了,无法安全地处理它们的释放。( 请参阅 makeCurrent。 )

    9.3.2 上下文获得函数

    QOpenGLContext *QOpenGLWidget::context() const
    
    • 1

       返回此widget使用的 QOpenGLContext,如果尚未初始化,则返回 0。 注意:通过 setParent() 重新设置widget的父对象会更改widget使用的上下文和帧缓冲对象。 另请参见 QOpenGLContext::setShareContext() 和 defaultFramebufferObject()。

    GLuint QOpenGLWidget::defaultFramebufferObject() const
    
    • 1

       返回帧缓冲对象句柄,如果尚未初始化,则返回 0。 注意:帧缓冲对象属于 context() 返回的上下文,可能无法从其他上下文访问。 注意:通过 setParent() 重新设置widget的父对象会更改widget使用的上下文和帧缓冲对象。此外,每次调整大小都会更改帧缓冲对象。 另请参见 context()。

    void QOpenGLWidget::doneCurrent()
    
    • 1

    释放上下文。

    在大多数情况下,不需要调用此函数,因为widget在调用 paintGL() 时会确保正确绑定和释放上下文。

    QSurfaceFormat QOpenGLWidget::format() const
    
    • 1

       返回此窗口widget及其顶层窗口使用的上下文和表面格式。

       在widget及其顶层窗口都创建、调整大小和显示之后,此函数将返回上下文的实际格式。

       如果请求不能被平台满足,实际格式可能与请求的格式不同。还可以获得比请求的颜色缓冲区大小更大的颜色缓冲区大小。 当窗口的OpenGL资源尚未初始化时,返回值是通过setFormat()设置的格式。( 另请参见setFormat()和context()。

    QImage QOpenGLWidget::grabFramebuffer()
    
    • 1

    渲染并返回一个32位RGB帧缓冲图像。

       注意:这是一个潜在的费时的操作,因为它依赖于glReadPixels()来读取像素。这可能会很慢,并且会停止GPU管线的执行。

    [virtual protected] void QOpenGLWidget::initializeGL()
    
    • 1

       这个虚函数在第一次调用 paintGL() 或 resizeGL() 之前被调用一次。需要在子类中重新实现此函数。

       这个函数应该设置所有必要的 OpenGL 资源和状态。在这里没有必要调用 makeCurrent(),因为在调用此函数时已经完成了上下文设置。但请注意,此时帧缓冲区还不可用,因此避免在此处发出绘图调用。请将这样的调用推迟到 paintGL() 中。(请参阅 paintGL() 和 resizeGL()。)

    bool QOpenGLWidget::isValid() const

    返回true,如果该widget和OpenGL资源(如上下文)已成功初始化。请注意,直到widget被显示之前,返回值始终为false。

    void QOpenGLWidget::makeCurrent()

    通过使相应的上下文当前并在该上下文中绑定帧缓冲对象,为此窗口widget件准备呈现OpenGL内容。

    在大多数情况下,不需要调用此函数,因为在调用 paintGL() 之前会自动调用它。

    另请参见 context()、paintGL() 和 doneCurrent()。

    [override virtual protected] void QOpenGLWidget::paintEvent(QPaintEvent *e)

    重新实现:QWidget::paintEvent(QPaintEvent *event)。

    处理绘制事件。

    调用QWidget::update()将导致发送绘制事件e,从而调用此函数(注意:这是异步的,将在返回update()之后的某个时刻发生)。此函数将在一些准备工作之后调用虚函数paintGL()来更新QOpenGLWidget的帧缓冲区内容。窗口的顶层将使用帧缓冲区的纹理与窗口的其余部分进行合成。

    [virtual protected] void QOpenGLWidget::paintGL()

    这个虚函数在需要绘制widget时调用,需要在子类中重新实现。

    没有必要调用makeCurrent(),因为在调用这个函数时已经完成了这个操作。

    在调用这个函数之前,上下文和帧缓冲已经绑定,视口通过调用glViewport()设置。框架不设置其他状态,不进行清除或绘制操作。

    参见initializeGL()和resizeGL()。

    [override virtual protected] void QOpenGLWidget::resizeEvent(QResizeEvent *e)

    重载:QWidget::resizeEvent(QResizeEvent *event)。

    处理传递给 e 事件参数的调整大小事件。调用虚函数resizeGL()。

    注意:避免在派生类中重写此函数。如果不可行,请确保也调用QOpenGLWidget的实现。否则,底层的framebuffer对象和相关资源将不会被正确调整大小,导致渲染不正确。

    [virtual protected] void QOpenGLWidget::resizeGL(int w, int h)

    这个虚函数在widget被调整大小时被调用,w和h表示新的大小。在子类中重新实现此函数。在此函数被调用时,makeCurrent()已经被调用,因此不需要再次调用。此外,framebuffer也已经被绑定。

    参见 initializeGL() 和 paintGL()。

    void QOpenGLWidget::setFormat(const QSurfaceFormat &format)

    此函数设置请求的表面格式。

    当未通过此函数显式设置格式时,将使用由QSurfaceFormat::defaultFormat()返回的格式。这意味着,当有多个OpenGLwidget时,可以通过在创建第一个widget之前单个调用QSurfaceFormat::setDefaultFormat()来替换对此函数的单独调用。

    注意:通过此函数请求alpha缓冲区不会产生期望的结果,当意图是使其他widget可见时。相反,使用Qt::WA_AlwaysStackOnTop启用带有其他widget可见下方的半透明QOpenGLWidget实例。但要记住,这会破坏堆叠顺序,因此将不再能够在QOpenGLWidget之上有其他widget。

    另请参阅format(),Qt::WA_AlwaysStackOnTop和QSurfaceFormat::setDefaultFormat()。

    void QOpenGLWidget::setTextureFormat(GLenum texFormat)

    这个函数设置纹理的自定义内部格式为texFormat。

    在使用sRGB帧缓冲时,需要指定一个格式,比如GL_SRGB8_ALPHA8。可以通过调用此函数来实现。

    注意:如果在widget已经被显示并且已经执行了初始化后调用此函数,则此函数无效。

    注意:此函数通常需要与调用QSurfaceFormat::setDefaultFormat()设置颜色空间为QSurfaceFormat::sRGBColorSpace的调用相结合使用。 该函数在Qt 5.10中引入。

    另请参见textureFormat()。

    void QOpenGLWidget::setUpdateBehavior(QOpenGLWidget::UpdateBehavior updateBehavior)

    将此widget的更新行为设置为updateBehavior。

    此函数是在Qt 5.5中引入的。

    另请参见updateBehavior()。

    GLenum QOpenGLWidget::textureFormat() const

    如果widget已经初始化,则返回活动的内部纹理格式;如果设置了请求格式但widget尚未显示,则返回请求的格式;如果未调用setTextureFormat()且widget尚未显示,则返回nullptr。

    此函数在Qt 5.10中引入。

    另请参见setTextureFormat()。

    QOpenGLWidget::UpdateBehavior QOpenGLWidget::updateBehavior() const

    返回部件的更新行为。

    这个函数在Qt 5.5中引入。

    参见setUpdateBehavior()。

  • 相关阅读:
    C++并发与多线程(2) | 线程运行开始和结束的基本方式
    4.1EF Core
    直播视频处理过程
    十八、商城 - 规格管理-模板管理(6)
    SMTP发送邮件时抱No appropriate protocol错误分析和解决方案
    AIGC实战——基于Transformer实现音乐生成
    Android Studio 导入工程&Gradle和JDK配置&修改工程名称&修改包名
    从ArcGIS两个DEM数据镶嵌结果错误说起——聊一聊像素类型和像素深度
    leetcode 137. 只出现一次的数字 II
    NFT Insider #64:电商巨头eBay提交NFT相关商标申请,毕马威将在Web3和元宇宙中投入3000万美元
  • 原文地址:https://blog.csdn.net/gongdiwudu/article/details/137872358