- PVSM.RU - https://www.pvsm.ru -

Использование кадрового буфера в Qt 5

Во времена Qt 4 можно было ускорить рисование QPainter с OpenGL используя класс QGLPixelBuffer [1]: он предоставляет удобный и быстрый способ создания поверхности для рисования, рендеринга на неё (с помощью обычных методов QPainter) и захвата конечного результата как QImage.
В Qt 5 QGLPixelBuffer по-прежнему существует, но он считается устаревшим в пользу объектов кадрового буфера, обёрнутых в Qt в класс QOpenGLFramebufferObject [2]. Однако QOpenGLFramebufferObject это не QPaintDevice, поэтому мы не можем использовать QPainter прямо на нём.

Введение QOpenGLPaintDevice

QOpenGLPaintDevice [3] является связующим звеном, которое нам нужно. Создание QOpenGLPaintDevice на активном контексте OpenGL разрешает нам рисовать на нём используя QPainter. Давайте посмотрим на пример его использования:

#include <QtCore>
#include <QtGui>
#include <QtWidgets>
 
QImage createImageWithFBO() {
    QSurfaceFormat format;
    format.setMajorVersion(3);
    format.setMinorVersion(3);
 
    QWindow window;
    window.setSurfaceType(QWindow::OpenGLSurface);
    window.setFormat(format);
    window.create();
 
    QOpenGLContext context;
    context.setFormat(format);
    if (!context.create()) qFatal("Cannot create the requested OpenGL context!");
    context.makeCurrent(&window);
 
    const QRect drawRect(0, 0, 400, 400);
    const QSize drawRectSize = drawRect.size();
 
    QOpenGLFramebufferObjectFormat fboFormat;
    fboFormat.setSamples(16);
    fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
 
    QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
    fbo.bind();
 
    QOpenGLPaintDevice device(drawRectSize);
    QPainter painter;
    painter.begin(&device);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing);
 
    painter.fillRect(drawRect, Qt::blue);
 
    painter.drawTiledPixmap(drawRect, QPixmap(":/qt-project.org/qmessagebox/images/qtlogo-64.png"));
 
    painter.setPen(QPen(Qt::green, 5));
    painter.setBrush(Qt::red);
    painter.drawEllipse(0, 100, 400, 200);
    painter.drawEllipse(100, 0, 200, 400);
 
    painter.setPen(QPen(Qt::white, 0));
    QFont font;
    font.setPointSize(24);
    painter.setFont(font);
    painter.drawText(drawRect, "Hello FBO", QTextOption(Qt::AlignCenter));
 
    painter.end();
 
    fbo.release();
    return fbo.toImage();
}
 
int main(int argc, char **argv) {
    QApplication app(argc, argv);
 
    QImage targetImage = createImageWithFBO();
 
    QLabel label;
    label.setPixmap(QPixmap::fromImage(targetImage));
    label.show();
    return app.exec();
}

Разбор

Этот небольшой фрагмент кода создаёт QImage, рисуя на временный объект буфера кадров с использованием QPainter, а затем отображает полученное изображение на QLabel. Давайте проанализируем, что делает метод createImageWithFBO.

QSurfaceFormat format;
format.setMajorVersion(3);
format.setMinorVersion(3);
 
QWindow window;
window.setSurfaceType(QWindow::OpenGLSurface);
window.setFormat(format);
window.create();
 
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) qFatal("Cannot create the requested OpenGL context!");
context.makeCurrent(&window);

Вначале мы создаём QWindow (с типом OpenGL) и QOpenGLContext, затем делаем контекст текущим в нашем окне. Они оба пытаются использовать OpenGL 3.3 (учтите, что Mac OS X на данный момент поддерживает OpenGL до 3.2).
Использование QWindow как результирующей поверхности контекста является воркэраундом, потому что на данный момент в Qt 5.0 нет никаких других типов QSurface. Так как нам нужна поверхность для установки текущего контекста, мы просто создаём невидимое окно без определённого размера и этого достаточно, чтобы иметь возможность использовать контекст OpenGL на нём.

const QRect drawRect(0, 0, 400, 400);
const QSize drawRectSize = drawRect.size();

Эти переменные будут содержать размер прямоугольника для рисования.

QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setSamples(16);
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
 
QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
fbo.bind();

Здесь мы создаём QOpenGLFramebufferObject с конечным размером. Прежде чем сдалать это, мы также указываем его формат: мы хотим сделать мультисэмплинг активным на нашем объекте кадрового буфера, таким образом мы получим сглаженные примитивы, поэтому мы задаём 16 сэмплов на пиксель; кроме того, мы просим Qt задействовать также буфер глубины и буфер шаблона. Мы не собираемся использовать их явно, но движок отрисовки Qt может, так как в нём есть вызовы glStencil [4].
В конце мы привязываем объект QOpenGLFramebufferObject (это переадресовывает все операции рисования на него).

QOpenGLPaintDevice device(drawRectSize);
QPainter painter;
painter.begin(&device);
painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing);
 
painter.fillRect(drawRect, Qt::blue);
 
painter.drawTiledPixmap(drawRect, QPixmap(":/qt-project.org/qmessagebox/images/qtlogo-64.png"));
 
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::red);
painter.drawEllipse(0, 100, 400, 200);
painter.drawEllipse(100, 0, 200, 400);
 
painter.setPen(QPen(Qt::white, 0));
QFont font;
font.setPointSize(24);
painter.setFont(font);
painter.drawText(drawRect, "Hello FBO", QTextOption(Qt::AlignCenter));
 
painter.end();

Далее мы приступаем к созданию QOpenGLPaintDevice и рисованию на нём с помощью обычных вызовов QPainter. Обратите внимание на то, что мы должны явно закончить рисование вызовом end(), после чего наш объект кадрового буфера будет содержать результат.

fbo.release();
return fbo.toImage();

Эти строки завершают магию: мы отвязываем объект кадрового буфера, сбросив таким образом все его настройки к стандартным, и захватываем его содержимое как QImage вызовом метода toImage().
Результат:

Использование кадрового буфера в Qt 5

Встраивание нативных вызовов OpenGL

Мы можем доработать этот простой пример и нарисовать что-нибудь вызовами «чистого» OpenGL во время рисования с QPainter. Достаточно добавить следующие строки в приведённый выше фрагмент, пока painter активен:

painter.beginNativePainting();
nativePainting();
painter.endNativePainting();

Пара вызовов begin/endNativePainting нужна, чтобы сбросить состояние OpenGL на «чистое», прежде чем мы выполним наше рисование. Функция nativePainting():

void nativePainting() {
    static const float vertexPositions[] = {
        -0.8f, -0.8f, 0.0f,
         0.8f, -0.8f, 0.0f,
         0.0f,  0.8f, 0.0f
    };
 
    static const float vertexColors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };
 
    QOpenGLBuffer vertexPositionBuffer(QOpenGLBuffer::VertexBuffer);
    vertexPositionBuffer.create();
    vertexPositionBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
    vertexPositionBuffer.bind();
    vertexPositionBuffer.allocate(vertexPositions, 9 * sizeof(float));
 
    QOpenGLBuffer vertexColorBuffer(QOpenGLBuffer::VertexBuffer);
    vertexColorBuffer.create();
    vertexColorBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
    vertexColorBuffer.bind();
    vertexColorBuffer.allocate(vertexColors, 9 * sizeof(float));
 
    QOpenGLShaderProgram program;
    program.addShaderFromSourceCode(QOpenGLShader::Vertex,
                                    "#version 330n"
                                    "in vec3 position;n"
                                    "in vec3 color;n"
                                    "out vec3 fragColor;n"
                                    "void main() {n"
                                    "    fragColor = color;n"
                                    "    gl_Position = vec4(position, 1.0);n"
                                    "}n"
                                    );
    program.addShaderFromSourceCode(QOpenGLShader::Fragment,
                                    "#version 330n"
                                    "in vec3 fragColor;n"
                                    "out vec4 color;n"
                                    "void main() {n"
                                    "    color = vec4(fragColor, 1.0);n"
                                    "}n"
                                    );
    program.link();
    program.bind();
 
    vertexPositionBuffer.bind();
    program.enableAttributeArray("position");
    program.setAttributeBuffer("position", GL_FLOAT, 0, 3);
 
    vertexColorBuffer.bind();
    program.enableAttributeArray("color");
    program.setAttributeBuffer("color", GL_FLOAT, 0, 3);
 
    glDrawArrays(GL_TRIANGLES, 0, 3);

Это чересчур подробный (но современный) способ для рисования трёхцветного треугольника в центре нашего изображения. Я не буду анализировать этот фрагмент кода подробно, так как он вполне читаем: вначале мы создаём два объекта буфера вершин, один для хранения позиции вершин (уже в усечённом пространстве), а другой для хранения цветов вершин (в RGB).
Затем мы создаём необходимые для вершин и фрагментов шейдеры, которые являются тривиальными — вершинный шейдер просто выводит (нормированные) позиции на выходе и передаёт цвета фрагментному шейдеру; фрагментный шейдер выводит цвета, которые он получает на входе.
Потом происходит связывание и буферы, созданные вначале устанавливаются как аттрибутные буферы.
Теперь всё готово для рисования нашего треугольника простым вызовом glDrawArrays. Конечный результат:

Использование кадрового буфера в Qt 5

Полный исходный код: pastebin.com/pkLA42G0 [5]

Динамические текстуры

Приложив немного больше усилий мы также можем продублировать возможности создания динамических текстур QGLPixelBuffer: QOpenGLFramebufferObject имеет метод texture(), который возвращает идентификатор текстуры, связанный с буфером цвета. Это означает, что мы можем рисовать текстуры, используя QPainter и накладывать их на любые объекты на нашей сцене OpenGL. Но это потребовало бы написания другой статьи…

Счастливого хакинга!

Автор: epicfailguy93

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/opengl/26804

Ссылки в тексте:

[1] QGLPixelBuffer: http://qt-project.org/doc/qt-4.8/qglpixelbuffer.html

[2] QOpenGLFramebufferObject: http://qt-project.org/doc/qt-5.0/qtgui/qopenglframebufferobject.html

[3] QOpenGLPaintDevice: http://qt-project.org/doc/qt-5.0/qtgui/qopenglpaintdevice.html

[4] так как в нём есть вызовы glStencil: http://code.woboq.org/qt5/qtbase/src/gui/opengl/qopenglpaintengine.cpp.html#915

[5] pastebin.com/pkLA42G0: http://pastebin.com/pkLA42G0

[6] Источник: http://habrahabr.ru/post/168911/