QGLWidget和快速离屏渲染

7
在Qt的QGLWidget中,是否可以完全离屏渲染而无需重新绘制场景到屏幕上,从而完全避免在监视器上翻转缓冲区?
我需要保存在帧缓冲区上生成的每一帧,但由于序列由4000帧组成,屏幕上的时间间隔为15ms,因此我需要花费4000 * 15ms = 60s,但我需要比60s快得多(计算不是瓶颈,只是更新是问题所在)。
离屏渲染帧缓冲区是否可以更快? 我可以避免QGLWidget中的监视器刷新率吗?
如何在帧缓冲区上完全渲染而不会出现缓慢的paintGL()调用?

QGLWidget::renderPixmap 有帮助吗? - Erbureth
交换缓冲区怎么样?http://qt-project.org/doc/qt-5.0/qtopengl/qglwidget.html#swapBuffers - Thadeux
@CrazyIvanovich:swapBuffers()不会帮助OP解决问题,因为交换间隔取决于VSync。此外,根据您要渲染的内容,实际上并不需要双缓冲。OP需要澄清他们正在渲染什么。 - thokra
我发现了一个名为QGL::IndirectRendering的QGL格式,它是用来做什么的? - linello
2个回答

5
目前我假设我们在谈论Qt4。
完全离屏渲染在QGLWidget中是否可能?
离屏渲染实际上并不是依赖于窗口系统的任务。至少在WGL和大多数工具包中的GLX中唯一的问题是,您无法拥有一个无表面的上下文,即一个没有绑定到窗口系统提供的可绘制项的上下文。换句话说,只要当前上下文存在,您将始终拥有由窗口系统提供的默认帧缓冲区,该帧缓冲区是不可变的。
有一些方法可以创建不需要手动创建窗口的上下文,但通常不值得麻烦。例如,对于EGL和OpenGL ES,这个问题不存在,因为有一个扩展专门解决这个问题,即离屏渲染。
但是,您可以在设置了有效上下文后简单地隐藏QGLWidget,并使用帧缓冲对象完成所有操作,而不涉及默认帧缓冲区的干预。
我的QGLWidget中是否可以避免监视器刷新率?
据我所知,Qt4的OpenGL模块没有手动关闭垂直同步的方法。您可以转向SDL或GLFW等工具包来实现这样的功能(不确定FreeGLUT)。
但是,您始终可以在驱动程序设置中关闭一些东西。这也会影响QGLWidget(或更好地说,底层窗口系统的交换行为)。
在帧缓冲区上进行离屏渲染是否会更快?
最终它真的不应该有所不同。您将想要将图像数据放在VRAM之外的某个地方,因此,在将当前帧渲染到FBO后,您需要获取图像。您可以将结果复制到前缓冲区(如果需要双缓冲区并交换到后缓冲区),或者在进一步处理当前帧之前需要先读取内容。
但是,就OpenGL和性能相关的任何事情而言,请勿猜测-请进行性能分析!
如何完全在帧缓冲区上进行渲染而无需慢速的paintGL()调用?
一旦设置了上下文,您根本不需要小部件。您可以在没有Qt干预的情况下自己完成所有操作。 paintGL()存在的唯一原因是提供易于使用的界面,保证在需要更新小部件时调用用户。
编辑:至于您在评论中的查询,请参见此最小代码示例,它应该跨平台工作,无需更改。
#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 Technologies Inc. AMD Radeon HD 7900系列 1.4(2.1(4.2.12337兼容性配置文件上下文13.101))
请注意,QGLWidget从未实际可见,也没有进行任何事件处理。Qt OpenGL库仅用于上下文创建。其他所有操作都不需要Qt的干预。只要根据您的需求设置视口和相关内容即可。
请注意:如果您只需要方便地设置上下文,您可能需要切换到比Qt4更轻量级的工具包,如FreeGLUT。就我个人而言,我发现在某些硬件上,例如Sandy Bridge CPU上,FreeGLUT在按照我想要的方式设置有效上下文方面要可靠得多。

让我详细理解你的答案。QGLWidget的技巧是: 1)设置小部件 2)使当前上下文成为当前上下文 3)设置FBO 4)隐藏QGLWidget 5)在渲染过程中循环,但在FBO上进行绘制对吗? - linello

0

我找到了一种解决方案,涉及使用QGLFrameBuffer对象和glReadPixels

首先,在QGLWidget :: initializeGL 中初始化我的QGLFrameBuffer对象,以便具有有效的GL上下文,其中QGLFrameBuffer可以“伪装”。

这是第一个实现。帧速率高达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(),那么垂直同步就完全无关紧要了,因此您的帧时间会提高一个数量级。 - thokra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接