QGLWidget 和快速的离屏渲染

Posted

技术标签:

【中文标题】QGLWidget 和快速的离屏渲染【英文标题】:QGLWidget and fast offscreen rendering 【发布时间】:2013-11-12 16:07:28 【问题描述】:

是否可以使用 Qt 在 QGLWidget 中完全渲染屏幕外,而无需将场景重新绘制到屏幕,从而完全避免缓冲区在监视器上翻转?

我需要保存在帧缓冲区上生成的每一帧,但是,由于序列由 4000 帧组成,并且屏幕上的时间间隔为 15ms,我花费了 4000*15ms=60s,但我需要比 60 秒快得多(计算不是这里的瓶颈,只是更新问题)。

在帧缓冲区上渲染离屏可以更快吗?我可以避免 QGLWidget 中的显示器刷新率吗?

如何在没有缓慢的paintGL() 调用的情况下完全在帧缓冲区上渲染?

【问题讨论】:

会QGLWidget::renderPixmap 帮忙吗? swapBuffers 怎么样? qt-project.org/doc/qt-5.0/qtopengl/qglwidget.html#swapBuffers @CrazyIvanovich:swapBuffers() 不会帮助 OP 修复他们的问题,因为交换间隔取决于 VSync。此外,根据您要渲染的内容,您实际上并不需要双缓冲。 OP 需要澄清他们正在渲染的内容。 我找到了一个名为QGL::IndirectRendering 的QGL 格式,它需要什么? 【参考方案1】:

现在我假设我们谈论的是 Qt4。

是否可以在 QGLWidget 中完全渲染到屏幕外

离屏渲染根本不是一个依赖于窗口系统的任务。在大多数工具包中,WGL(至少)和 GLX 的唯一问题是您不能拥有 无表面上下文,即未绑定到窗口系统提供的可绘制对象的上下文。换句话说,只要当前上下文存在,您将始终拥有窗口系统提供的 默认帧缓冲区,该帧缓冲区是不可变的。

有一些方法可以使用 X11 手动创建不需要 窗口 的上下文,但通常不值得这么麻烦。例如,对于 EGL 和 OpenGL ES,这个问题不存在,因为有一个扩展正是针对这个问题的,即离屏渲染。

但是,您可以在设置有效上下文后简单地隐藏 QGLWidget,并使用帧缓冲区对象完成所有操作,而无需默认帧缓冲区干预。

我可以避免 QGLWidget 中的显示器刷新率吗?

不,据我所知,Qt4 的 OpenGL 模块无法以编程方式打开 vsync。你可以求助于 SDL 或 GLFW 类似的东西(不确定 FreeGLUT)。

不过,您始终可以在驱动程序设置中关闭某些功能。这也会影响 QGLWidget(或者更好的说法,底层窗口系统的交换行为。)

在帧缓冲区上渲染离屏可以更快吗?

最后真的应该没关系。您将需要 VRAM 以外的其他地方的图像数据,因此在将当前帧渲染到 FBO 之后,无论如何您都需要获取图像。您可以将结果 blit 到前端缓冲区(如果需要双缓冲和交换,则将结果传送到后端缓冲区),或者您需要在进一步处理当前帧之前读回内容。

但是,与 OpenGL 和性能相关的任何事物一样,不要猜测 - 配置文件

如何在没有缓慢的 paintGL() 调用的情况下完全在帧缓冲区上渲染?

一旦设置了上下文,您就根本不需要小部件。您可以在没有 Qt 干预的情况下自己完成所有的魔法。 paintGL() 存在的唯一原因是为用户提供一个易于使用的界面,保证在需要更新小部件时调用该界面。

编辑:关于您在 cmets 中的查询,请参阅这个最小的代码示例,它应该可以跨平台工作而无需更改。

#include <iostream>
#include <QtOpenGL/QGLWidget>
#include <QtGui/QApplication>

void renderOffScreen ()

  std::cout << glGetString(GL_VENDOR)   << std::endl;
  std::cout << glGetString(GL_RENDERER) << std::endl;
  std::cout << glGetString(GL_VERSION)  << std::endl;

  // do whatever you want here, e.g. setup a FBO, 
  // render stuff, read the results back until you're done
  // pseudocode:
  //     
  //      setupFBO();
  //   
  //      while(!done)
  //      
  //        renderFrame();
  //        readBackPixels();
  //        processImage();
  //      


int main(int argc, char* argv[])

  QApplication app(argc, argv);
  QGLWidget gl;

  // after construction, you should have a valid context
  // however, it is NOT made current unless show() or
  // similar functions are called
  if(!gl.isValid ())
  
    std::cout << "ERROR: No GL context!" << std::endl;
    return -1;
  

  // do some off-screen rendering, the widget has never been made visible
  gl.makeCurrent (); // ABSOLUTELY CRUCIAL!
  renderOffScreen ();

  return 0;

在我当前的机器上,程序打印:

ATI 技术公司

AMD Radeon HD 7900 系列

1.4(2.1(4.2.12337 兼容性配置文件上下文 13.101))

请注意QGLWidget 从未真正可见,也不会进行任何事件处理。 Qt OpenGL 库仅用于创建上下文。其他任何事情都无需 Qt 干预即可完成。只是不要忘记根据您的需要设置视口和内容。

请注意:如果您只需要一些方便的方法来设置上下文,您可能希望切换到一些比 Qt4 更轻量级的工具包,例如 FreeGLUT。就我个人而言,我发现 FreeGLUT 在按照我想要的方式在某些硬件上设置有效上下文时更加可靠,例如Sandy Bridge CPU。

【讨论】:

让我明白你很详细的答案。 QGLWidget 的诀窍是 1) 设置小部件 2) 使当前上下文成为当前上下文 3) 设置 FBO 4) 隐藏 QGLWidget 5) 在渲染过程中循环但在 FBO 上绘图对吗?【参考方案2】:

我找到了涉及使用QGLFrameBuffer 对象和glReadPixels 的解决方案。

首先我在QGLWidget::initializeGL 中初始化我的QGLFrameBuffer 对象,以便在QGLFrameBuffer 可以“撒谎”的地方拥有一个有效的GL 上下文。

这是第一个实现。帧率是10 倍,并且不会根据 VSync 更新任何内容!!

MyGLWidget::MyGLWidget(QWidget *parent) :
    //    QGLWidget(parent)
    QGLWidget( QGLFormat(QGL::SampleBuffers), parent) //this format doesn't matter it's the QGLWidget format on the monitor

    //some initializations




void MyGLWidget::initializeGL()


    qglClearColor(Qt::black);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0,0,-10);

    this->makeCurrent();

    // Initializing frame buffer object
    // Here we create a framebuffer object with the smallest necessary precision, i.e. GL_LUMINANCE in order to make
    // the subsequent calls to glReadPixels MUCH faster because the internal format is simpler and no casts are needed
    QGLFramebufferObjectFormat fboFormat;
    fboFormat.setMipmap(false);
    fboFormat.setSamples(0);
    fboFormat.setInternalTextureFormat(GL_LUMINANCE);
    // Create the framebuffer object
    fbo = new QGLFramebufferObject(QSize(this->width(),this->height()),fboFormat);



void MyGLWidget::generateFrames()

    //keep unsigned int because of possible integer overflow 
    //when resizing the vector and consequent std::bad_alloc() exceptions
    unsigned int slicesNumber = 1000;
    unsigned int w = this->width();
    unsigned int h = this->height();

    // This vector contains all the frames generated as unsigned char.
    vector<unsigned char> allFrames;
    allFrames.resize(w*h*slicesNumber);

    fbo->bind();
    // Inside this block the rendering is done on the framebuffer object instead of the MyGLWidget
    for ( int i=0; i<slicesNumber; i++ )
    
            this->paintGL();
            // Read the current frame buffer object
            glReadPixels(0, 0, w, h, GL_LUMINANCE, GL_UNSIGNED_BYTE, allFrames.data()+i*w*h);
        // update scene()
    
    fbo->release();

【讨论】:

无需在initializeGL() 内调用makeCurrent()。在调用后者时,上下文已经成为当前的。此外,您似乎没有删除您在堆上创建的QGLFramebufferObject - 除非它是在子类中完成的'dtor,这在您的示例中不可见。此外,您获得一个数量级的帧时间也就不足为奇了。如果你不swapBuffers(),vsync 完全无关紧要。

以上是关于QGLWidget 和快速的离屏渲染的主要内容,如果未能解决你的问题,请参考以下文章

离屏 Canvas 原理与 SpriteBuffer

开始我的GL离屏渲染绑定[转]

OpenGL 中的离屏帧缓冲区

鲁大师安卓新版:新增离屏压力测试,你的手机分数变了多少?

离屏渲染

离屏渲染,OpenGL的三种渲染方式