1

I am trying to figure out how to use OpenGL with QtQuickPaintedItem. According to the documentation, this seems to be possible, and there are several "helper" classes for that.

I tried this code below, and I also tried to create QOpenGLPaintDevice, QOpenGLContext, and QOpenGLFramebufferObject and pass their instances to their functions in many combinations, but it did not work either.

/// Simple3d.h

#ifndef SIMPLE3D_H
#define SIMPLE3D_H

#include<QtQuick/QQuickPaintedItem>
#include<QOpenGLFunctions>

// Not sure if I need to use any of these classes.
//#include<QOpenGLPaintDevice>
//#include<QOpenGLContext>
//#include<QOpenGLFramebufferObject>
//#include<QOpenGLShaderProgram>
//#include <GL/gl.h>

/** @brief inherets from QtQuickItem and it draws
 * triangle.
 *
 */
class Simple3d : public QQuickPaintedItem
{

Q_OBJECT

public:

    /** Call this function to be able to instantiate this
     * item in a QML file.
     */
    static void register_qml_item();

    Simple3d();

    void paint(QPainter * painter) override;

    virtual ~Simple3d() override;

private:
    bool mDebug;

    /** OpenGL stuff */
    bool mOpenGlInitialized;
    GLuint mProgramId;
    static const GLuint mVertexArrayID = 0;
    QOpenGLFunctions *mGlFuncs;

    // Not sure when is a good time to update these:
    GLsizei mWidth, mHeight;

    GLuint load_shader(GLenum type, const char *shaderSrc);
    void verify_initialized();
    void draw_open_gl();
};

#endif

/// Simple3d.cpp

#include "simple3d.h"
#include <QSGGeometryNode>
#include <QSGVertexColorMaterial>
#include <QtMath>
#include<QOpenGLFunctions>
#include<QPainter>
#include<QGuiApplication>

using namespace std;

Simple3d::Simple3d()
{
    mDebug = true;
    mOpenGlInitialized = false;
    mGlFuncs = nullptr;
    setFlag(ItemHasContents, true);
}

void Simple3d::paint(QPainter *painter)
{

    if (mDebug) qDebug("Simple3d is running paint()");

    if (!mGlFuncs){
        mGlFuncs = new QOpenGLFunctions();
        mGlFuncs->initializeOpenGLFunctions();
    }

    painter->beginNativePainting();

    verify_initialized();
    draw_open_gl();
    painter->endNativePainting();

    if (mDebug) qDebug("Simple3d finished rendering.");
}

GLuint Simple3d::load_shader(GLenum type, const char *shaderSrc)
{
    GLuint shader;
    GLint compiled;
    // Create the shader object
    shader = mGlFuncs->glCreateShader(type);
    if(shader == 0)
        return 0;
    // Load the shader source
    mGlFuncs->glShaderSource(shader, 1, &shaderSrc, nullptr);
    // Compile the shader
    mGlFuncs->glCompileShader(shader);
    // Check the compile status
    mGlFuncs->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if(!compiled)
    {
        GLint infoLen = 0;
        mGlFuncs->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 1)
        {
            char* infoLog = new char[size_t(infoLen)+1];
            mGlFuncs->glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
            qDebug("In the shader source:\n%s", shaderSrc);
            qDebug("Error compiling shader:\n%s\n", infoLog);
            delete [] infoLog;
        }
        mGlFuncs->glDeleteShader(shader);
        return 0;
    }
    return shader;
}

void Simple3d::verify_initialized()
{
    if (mOpenGlInitialized) return;
    // This piece is partially taken from:
    // https://www.khronos.org/assets/uploads/books/openglr_es_20_programming_guide_sample.pdf

    mWidth = 50;
    mHeight = 50;

    const char vShaderStr[] =
            "attribute vec4 vPosition;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vPosition;\n"
            "}\n";
    const char fShaderStr[] =
            "//precision mediump float;\n"
            "void main()\n"
            "{\n"
            " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
            "}\n";
    GLuint vertexShader;
    GLuint fragmentShader;
    GLint linked;
    // Load the vertex/fragment shaders
    vertexShader = load_shader(GL_VERTEX_SHADER, vShaderStr);
    fragmentShader = load_shader(GL_FRAGMENT_SHADER, fShaderStr);
    // Create the program object
    mProgramId = mGlFuncs->glCreateProgram();
    if(mProgramId == 0) {
        qDebug("Failed to create the OpenGL program.");
        return;
    }
    mGlFuncs->glAttachShader(mProgramId, vertexShader);

    mGlFuncs->glAttachShader(mProgramId, fragmentShader);
    // Bind vPosition to attribute 0
    mGlFuncs->glBindAttribLocation(mProgramId, mVertexArrayID, "vPosition");
    // Link the program
    mGlFuncs->glLinkProgram(mProgramId);
    // Check the link status
    mGlFuncs->glGetProgramiv(mProgramId, GL_LINK_STATUS, &linked);
    if(!linked)
    {
        qDebug("Failed to link OpenGL program.");
        GLint infoLen = 0;
        mGlFuncs->glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 1)
        {
            char* infoLog = new char[size_t(infoLen)];
            mGlFuncs->glGetProgramInfoLog(mProgramId, infoLen, nullptr, infoLog);
            qDebug("Error linking program:\n%s\n", infoLog);
            delete [] infoLog;
        }
        mGlFuncs->glDeleteProgram(mProgramId);
        return;
    }
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    mOpenGlInitialized = true;
    qDebug("OpenGL was initialized successfully.");
    return;
}

void Simple3d::draw_open_gl()
{

    if (!mOpenGlInitialized){
        QGuiApplication::exit(1);
        return;
    }

    mGlFuncs->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    mGlFuncs->glEnable(GL_DEPTH_TEST);

    mGlFuncs->glUseProgram(mProgramId);

    GLfloat vVertices[] =
        {0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f};

    // Set the viewport
    mGlFuncs->glViewport(0, 0, mWidth, mHeight);

    // Clear the color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Load the vertex data
    mGlFuncs->glVertexAttribPointer(mVertexArrayID, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    mGlFuncs->glEnableVertexAttribArray(0);
    mGlFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
    mGlFuncs->glFlush();
    mGlFuncs->glFinish();
    mGlFuncs->glUseProgram(0); // release the program.
}

Simple3d::~Simple3d()
{

}

void Simple3d::register_qml_item()
{
    qmlRegisterType<Simple3d>("CustomGraphicsItems", 1, 0, "Simple3d");
}

/// main.cpp

#include "simple3d.h"
#include <stdexcept>

using namespace std;

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

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    Simple3d::register_qml_item();

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/example_Simple3d.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                    &app, [url](QObject *obj, const QUrl &objUrl)
    {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    int num_root_objects = engine.rootObjects().size();
    qDebug("The number of root objects: %i", num_root_objects);

    if (engine.rootObjects().size()!=1){
        throw logic_error("Expected exactly one root object"
                          "in the qml file");
    }
    QObject * root = engine.rootObjects()[0];

    Simple3d * draw_3d_item = root->findChild<Simple3d*>("draw_3d_item");
    if (!draw_3d_item){
        throw logic_error("Did not find item 'draw_3d_item'.");
    }

    int result = app.exec();

    qDebug("The app is exiting.");
    return result;
}

/// example_Simple3d.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import CustomGraphicsItems 1.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12

Window {
    visible:  true
    Simple3d{
        id: draw_3d_item
        width: 50
        height: 50
        anchors.fill: parent
        objectName: "draw_3d_item"
    }
}

The code should display a red triangle on black background, but nothing is being drawn.

andrew
  • 11
  • 2

1 Answers1

0

This question is similar to this: Rendering custom opengl in qt5's qtquick 2.0

I liked the answer by Gunnar Sletta. The trick is to use QQuickFramebufferObject, not QtQuickPaintedItem.

The first lines of QQuickFramebufferObject reference page say that it is an auxiliary class, but in fact it is the key class if you want to create a custom OpenGL within QtQuick framework. They should have called it QQuickOpenGLItem.

Just read this is the example of using QQuickFramebufferObject: https://doc.qt.io/qt-5/qtquick-scenegraph-textureinsgnode-example.html (Click on link "Example project @ code.qt.io".)

andrew
  • 11
  • 2