Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Render Custom Texture to QQuickWindow



  • Hi,

    My application is supposed to render an opengl texture I manually created in a QQuick Window.

    The code consists of a class TextureProvider, which inherits from QQuickItem and is instantiated in QML. The class reimplements the updatePaintNode(...) routine to add my texture node and create image data.

    The texture is created in a custom GLTexture class, that subclasses QSGTexture. Everytime the updatePaintNode(...) routine of TextureProvider is called, I create a new grayscale texture with slightly increased intensities and try to render it to a slightly different position. It seems like everything is working as expected, the rendering thread calls the bind method of my texture class during rendering, and the texture is also valid (I can download it and wrap it in a qt image inside the bind function).

    In my application window I can only see a black square instead of a grayscale one though.
    This maybe a little confusing, so please have a look at my minimal example to get a better understanding of the problem:
    In case you don´t have Opengl32.lib, you can leave it out. I only need it in textureprovider.h:108 to to get the pixel data from the texture and save it to disk. Uncomment the line/block and it should be fine.

    rendercontrol.pro

    TEMPLATE = app
    
    QT += quick qml gui
    
    SOURCES += main.cpp \
               textureprovider.cpp
    
    HEADERS += textureprovider.h
    
    LIBS += Opengl32.lib
    
    RESOURCES += rendercontrol.qrc
    

    main.cpp

    #include <QGuiApplication>
    #include "textureprovider.h"
    #include <QQmlApplicationEngine>
    #include <QSurfaceFormat>
    
    int main(int argc, char **argv)
    {
        QGuiApplication app(argc, argv);
    
        QSurfaceFormat sfc;
        sfc.setMajorVersion(3);
        sfc.setMinorVersion(3);
        sfc.setProfile(QSurfaceFormat::CoreProfile);
    
        QSurfaceFormat::setDefaultFormat(sfc);
    
        qmlRegisterType<DUMMY::TextureProvider>("dummy.texture.provider", 1 ,0, "GLTextureSrc");
    
        QQmlApplicationEngine eng(QUrl("qrc:/rendercontrol/demo.qml"));
    
        return app.exec();
    }
    
    

    textureprovider.h

    #ifndef TEXTUREPROVIDER_H
    #define TEXTUREPROVIDER_H
    
    #include <QQuickItem>
    #include <QSGTexture>
    #include <QtGui/QOpenGLFunctions_3_3_Core>
    #include <QOpenGLFunctions>
    #include <QTimer>
    
    #include <gl/GL.h>
    
    #include <vector>
    
    constexpr int BYTE_MAX = 255;
    
    namespace DUMMY
    {
        class GLTexture : public QSGTexture
        {
        public:
    
            GLTexture(bool recreate = false) :
                m_width (300)
              , m_height(300)
              , m_data_gray(std::vector<unsigned char>(m_width * m_height * 4, (unsigned char)200))
            {
                m_texsz = QSize(m_width, m_height);
    
                if(recreate)
                     createTextures(true);
            }
    
            void createTextures(bool recreate = false)
            {
                static int pxl_val;
    
                m_api = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
    
                if(m_texid0 == 0 || recreate)
                {
                // create data for the texture -> grayscale image with increasing intensities
                // Modulo BYTE_MAY since to ensure pixel values are in range [0; 255]
                m_data_gray = std::vector<unsigned char>(m_width * m_height * 4, (unsigned char)(pxl_val % BYTE_MAX));
                pxl_val+=20;
    
                m_api->initializeOpenGLFunctions();
    
                m_api->glGenTextures(1, &m_texid0);
    
                m_api->glBindTexture(GL_TEXTURE_2D, m_texid0);
    
                m_api->glTexImage2D(GL_TEXTURE_2D
                                 , 0
                                 , GL_RGBA
                                 , m_width
                                 , m_height
                                 , 0
                                 , GL_RGBA
                                 , GL_UNSIGNED_BYTE
                                 , m_data_gray.data());
    
                m_api->glBindTexture(GL_TEXTURE_2D, 0);
                }
            }
    
            bool
            hasAlphaChannel() const override
            {
                return true;
            }
    
            bool
            hasMipmaps() const override
            {
                return false;
            }
    
            int
            textureId() const override
            {
                return m_texid0;
            }
    
            QSize
            textureSize() const override
            {
                return m_texsz;
            }
    
            void
            bind() override
            {
                auto glcontext = QOpenGLContext::currentContext();
                assert(glcontext);
    
                auto fence0  = m_api->glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
                               m_api->glClientWaitSync(fence0, 0, 2000000000); //timeout 2s
    
                m_api->glBindTexture( GL_TEXTURE_2D, m_texid0 );
    
                /*
                save image to disk -> this is working!
                */
                {
                static int cnt;
                std::vector<uchar> vec(m_width * m_height * 4, (uchar)0);
    
                ::glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, vec.data());
    
                QImage im(vec.data(), m_width, m_height, QImage::Format_RGBA8888);
                im.save(std::string("image_" + std::to_string(cnt++) + ".png").c_str());
                }
            }
    
            QOpenGLFunctions_3_3_Core*  m_api;
            QSize                       m_texsz;
            int                         m_width;
            int                         m_height;
            std::vector<unsigned char>  m_data_gray;
            GLuint                      m_texid0 = 0;
        };
    
    
    
    
        class TextureProvider : public QQuickItem
        {
        public:
            TextureProvider();
    
            ~TextureProvider();
    
        protected:
            QSGNode* updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
    
            DUMMY::GLTexture*           m_tex;
            QTimer                      m_timer;
    
        };
    }
    #endif // TEXTUREPROVIDER_H
    
    

    textureprovider.cpp

    #include "textureprovider.h"
    #include <QSGSimpleRectNode>
    #include <QSGSimpleTextureNode>
    #include <QQuickWindow>
    
    DUMMY::TextureProvider::TextureProvider() : m_tex(new DUMMY::GLTexture())
    {
        setFlag(TextureProvider::ItemHasContents, true);
    
        m_timer.setInterval(128);
    
        connect(&m_timer, &QTimer::timeout, this, &QQuickItem::update);
    
        m_timer.start();
    }
    
    DUMMY::TextureProvider::~TextureProvider()
    {
        delete m_tex;
    }
    
    QSGNode* DUMMY::TextureProvider::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
    {
        QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node);
    
        if (!n) {
            n = new QSGSimpleTextureNode();
    
            n->setOwnsTexture(true);
    
            auto tex = new DUMMY::GLTexture(true);
            n->setTexture(tex);
        }
    
        auto t = static_cast<DUMMY::GLTexture*>(n->texture());
    
        // create new texture for rendering
        t->createTextures(true);
    
        static int offset;
        if(offset > 300)
        offset = 0;
    
        //Apply offset to rectangle to see what happens on the screen
        n->setRect(offset
                 , offset
                 , m_tex->textureSize().width()
                 , m_tex->textureSize().height());
    
        offset+=20;
    
        n->markDirty(QSGNode::DirtyMaterial);
    
        return n;
    }
    
    

    demo.qml

    import QtQuick 2.0
    import QtQuick.Window 2.0
    
    import dummy.texture.provider 1.0
    
    Window {
        visible: true
        minimumWidth: 1024
        minimumHeight: 768
    
       GLTextureSrc{
        id: texSrc
        width: 300
        height: 300
        objectName: "texture_ren"
        }
    }
    

    rendercontrol.qrc

    <RCC>
        <qresource prefix="/rendercontrol">
            <file>demo.qml</file>
        </qresource>
    </RCC>
    

    This is what my output window looks like.
    rendercontrol.png

    This is a sequence of recttangles I saved inside the bind() function (line 104 - 124 inside textureprovider.h) to disk and what the rectangle in the window should actually look like:
    rendercontrol-seq.png

    Why can I donwload the pixel data from the texture inside the bind function (line 104 - 124 inside textureprovider.h) but I don´t get the expected output to the window? The texture seems to be valid in the redering thread and was created using the correct context.

    Any help is much appreciated!
    Thanks!

    My system config:
    Windows 10
    QT 5.12.5
    I tested on an AMD RADEON PRO WX5100 as well as on an Intel HD Graphics 630 gpu.

    Best,



  • @tssm said in Render Custom Texture to QQuickWindow:

    n->setRect(offset
    , offset
    , m_tex->textureSize().width()
    , m_tex->textureSize().height());

    Have you set the source rect? I had issues in my code with that:

    n->setSourceRect(0
                 , 0
                 , m_tex->textureSize().width()
                 , m_tex->textureSize().height());
    

    I don't know about the opengl part. You could create a texture using an image as a canvas and use QPainter on it to test there isn't a problem with your QSG code. I have not done very much opengl using Qt itself.

    
                QImage canvas(rect.width(), rect.height(), QImage::Format_RGBA8888);
                canvas.fill(QColor("transparent"));
                QPainter painter(&canvas);
    
                QFont font = painter.font();
                //font.setPixelSize(48);
                font.setPixelSize(rect.width());
                font.setBold(true);
                painter.setFont(font);
                painter.setPen(color);
    
                QRect bounding = QRect(0, 0, rect.width(), rect.height());
                painter.drawText(0, 0, rect.width(), rect.height(), Qt::AlignCenter, ch, &bounding);
    
                QSGTexture *texture = this->window()->createTextureFromImage(canvas);
    


  • Thanks for the hint! I tried and you are right. It is working when creating the texture as a canvas. The

    n->setSourceRect(0
                 , 0
                 , m_tex->textureSize().width()
                 , m_tex->textureSize().height());
    

    function does not influence the result. That means my implementation of QSGTexture is the problem, but I don´t see how to narrow down the search from there...



  • Did you ever got this fixed?


Log in to reply