Qt6 QSGTexture not shown inside QQuickItem using using a custom shader classes

I try to show a simple texture through opengl and Qt6 using custom shader classes.

  • The image is a QImage encoded with Format_RGBA8888. I want this image to be shown on a custom QQuickItem.
  • This custom class uses a custom QSGNode class inside the QQuickItem::updatePaintNode function.
  • The custom QSGNode uses custom QSGMaterial and QSGMaterialShader classes too.
  • The QSGMaterialShader has 2 shader files, one for the fragment shader and the other for the vertex one.

For the example I created a QTimer which updates the source image every 5 seconds.

What I expect when I launch the application is to see the image on the UI.
But a fully black image was shown even if I wait for the image to be updated through the timer event.

First, to generate the image, I added a function inside my MainWindow class:
This function is called by the timer each time it reaches its timeout. The returned image is finally stored inside the custom QQuickItem instance.

MainWindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    ui->setupUi(this);
    // register the custom C++ QQuickItem to be used by the QML
    qmlRegisterType<OP_ImageViewer>("OP", 1, 0, "OP_ImageViewer"); // register the custom image viewer

    // set source to 'main.qml'
    // ...

    QTimer* t = new QTimer(); // simulate the image change
    t->setInterval(5000);
    t->callOnTimeout([this]{
        auto i = updateImage();
        quickWidget->rootObject()->setProperty("sourceImage", QVariant::fromValue(i));
    });
    t->start();
}

QImage MainWindow::updateImage()
{
    QSize sourceSize(512, 512);
    QImage sourceImage(sourceSize, QImage::Format_RGBA8888);

    // Loop through each pixel in the image
    for (int y = 0; y < sourceSize.height(); ++y) {
        for (int x = 0; x < sourceSize.width(); ++x) {
            // Calculate the normalized gradient value
            double gradient = (static_cast<double>(x) + y) / (sourceSize.width() + sourceSize.height() - 2);
            // Scale the gradient to the maximum grayscale value (255 for Format_RGBA8888)
            int grayValue = static_cast<int>(gradient * 255);
            // Set the pixel value

            if(count%2)
                sourceImage.setPixelColor(x, y, QColor(255-grayValue, 0, grayValue));
            else
                sourceImage.setPixelColor(x, y, QColor(grayValue, 0, 255-grayValue));
        }
    }
    count++; // Update the counter for image content update on timer event

    return sourceImage;
}

The QMl file is made to show the custom QQuickItem to the user.

main.qml

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

import OP 1.0

Rectangle {
    width: 800
    height: 600
    color: "transparent"

    property var sourceImage

    OP_ImageViewer {
        anchors.fill: parent
        anchors.margins: 5
        image: sourceImage
    }

The custom QQuickItem is updated each time the source image is set. So the updatePaintNode is called automatically.
Inside this function, I use my custom QSGGeometryNode using the *oldNode argument.

OP_ImageViewer.h (custom QQuickItem)

class OP_ImageViewer : public QQuickItem
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QImage image WRITE setImage)

public:

    OP_ImageViewer(QQuickItem *parent = nullptr) 
    {
        setFlag(ItemHasContents, true);
    }

    void setImage(const QImage &image) {
        if (image.isNull())
            return;// a null image doesn't update the item

        m_sourceImage = image;
        update();
    }

protected:
    QImage m_sourceImage; // the image used for the texture

    QSGNode *OP_ImageViewer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override
    {
        ColormapNode* node = nullptr;
        if(oldNode == nullptr)
            node = new ColormapNode();
        else
            node = static_cast<ColormapNode*>(oldNode);
    
        // update geometry
        // ...

        // update the source image
        node->setTexture(window()->createTextureFromImage(m_sourceImage,  QQuickWindow::TextureIsOpaque));

        return node;
    }
}

The setTexture function is a custom function which will delete the old texture (if one already exist) and set the given QSGTexture, inside the custom QSGMaterial instance

ColormapNode class inherited from QSGGeometryNode


class ColormapNode : public QSGGeometryNode
{
public:

    // The constructor instances itself the needed objects
    // The material, and the geometry

    // Some Class content

    void setTexture(QSGTexture* texture, bool updateUI = true)
    {
        if(updateTexture(m_texture, texture)) // the function replace the m_texture instance with the texture one, if both textures are equal, the function returns false, otherwise return true
            m_material->state.texture = texture;

        if(updateUI)
            markDirty(QSGNode::DirtyMaterial);
    }

private:
    ColormapMaterial* m_material = nullptr;
    QSGTexture* m_texture = nullptr;
}

The m_material is of type ColormapMaterial which is my custom QSGMaterial class.

ColormapMaterial class inherited from QSGMaterial

class ColormapMaterial : public QSGMaterial
{
public:
    ColormapMaterial() { }
    QSGMaterialType *type() const override; // instances a unique QSGMaterualType instance
    int compare(const QSGMaterial *other) const override; // custom compare

    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
    {
        return new ColormapShader; // return a custom shader instance
    }

    struct {
        // stuff...
        QSGTexture *texture = nullptr;
    } state;
};

So this class allows me to instanciate a custom QSGMaterialShader class to update the sampled image using the QSGMaterialShader::updateSampledImage override.

ColormapShader class inherited from QSGMaterialShader

class ColormapShader : public QSGMaterialShader
{
public:
    ColormapShader()
    {
        setShaderFileName(FragmentStage, QLatin1String(":/Shaders/Sources/imgh_colormap.frag.qsb"));
        // set the vertex file too
    }

    void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
                            QSGMaterial *newMaterial, QSGMaterial *) override
    {
        Q_UNUSED(state);

        ColormapMaterial *mat = static_cast<ColormapMaterial *>(newMaterial);
        switch (binding) { // the binding for the sampler2Ds in the fragment shader
        case 1:
            *texture = mat->state.texture;
            break;
        // Other cases
        default:
            return;
        }
    }

Finally, there is the fragment shader file where the texture is processed by opengl.

imgh_colormap.frag

#version 440

layout(location = 0)in vec2 vTexCoord;
layout(location = 0) out vec4 fragColor;

layout(binding = 1) uniform sampler2D opTexture;

void main()
{
    fragColor = texture(opTexture, vTexCoord);
}

Adding to those information, I made a standalone application on git if you want to test it easily
https://gitlab.com/adebono/qt6-porting-stackoverflow-example.

screenshots

I did those screenshots to image the problem. You can easily reproduce them using my git project
What I see using my custom Shader

What I should see

My hypothesis is:

I think the issue I encounter is during the QImage conversion to QSGTexture from the OP_ImageViewer class as the QSGTexture instance seems to be empty. To verify it I tried to retrieve the current OpenGL context inside the updatePaintNode function but the instance is always nullptr. As said inside the Qt’s doc:

Returns the last context which called makeCurrent in the current thread, or nullptr, if no context is current.

So did I missed to call the makeCurrent(...) function somewhere ?

Thank you in advance for your answers.

New contributor

adebono is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật