Porting Qt5’s application to Qt6 using custom OpenGL class

I’m working for a company where we developed an application based on Qt5.15 c++/QML.
Recently we decided to port this application to the new Qt’s version 6.7.x.
I made the changes to remove all the deprecated stuff. Now I’m blocked when I try to make my custom OpenGL implementations work.

The Goal:

My application retrieve an image through a connection with a second application. This image must be displayed inside the application to allow the user to see it live. this image is a Grasycale16 format one, embed a contrast software solution, and a colormap colorization. By default, the image is shown as a Grayscale16 format, but could be colorized with a “Jet” colormap gradient.

The solution:

To achieve it in Qt5 framework, we created a custom QSGSimpleMaterialShader which isn’t ported to Qt6. So I followed Qt’s tutorials to create and manage custom Textures and Materials using QSGTexture, QSGMaterial, QSGMaterialShader and QSGGeometricalNode.

The issue:

When I finally succeeded to compile my ported application and start the QML window I saw that the QQuickItem wasn’t drawn. After several hours of debugging/Google searches/GPT requests, I tried to colorized my QQuickItem with a vec4 OpenGL’s color by modifying the *.frag file. but when I try to consider my uniform sampler2D object, the result is always “transparent”.

The code

Following, some contextual informations:

myShader.frag

#version 440

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

layout(std140, binding = 0) uniform buf {
    mat4 qt_Matrix;
    float qt_Opacity;
} ubuf;

layout(binding = 1) uniform sampler2D opTexture; // The source image
layout(binding = 2) uniform sampler2D colormap; // The colormap to burn

void main()
{
    // Sample the opTexture using the texture coordinates
    vec4 opColor = texture(opTexture, vTexCoord.st);

    // Use the red channel of the opTexture sample to lookup in the colormap
    // fragColor = vec4(1, 1, 1, 1); // Works
    // fragColor = vec4(vTexCoord.xy, 1, 1); //Works
    fragColor = texture(colormap, vec2(opColor.r, 0.0)) * ubuf.qt_Opacity; //Doesn't work
}

myShader.vert

#version 440

layout(location = 0) in vec4 qt_Vertex;
layout(location = 1) in vec4 qt_MultiTexCoord0;

layout(std140, binding = 0) uniform buf {
    mat4 qt_ModelViewProjectionMatrix;
    float qt_Opacity;
} ubuf;

layout(location = 0) out vec4 qt_TexCoord0;

out gl_PerVertex { vec4 gl_Position; };

void main(void)
{
    gl_Position = ubuf.qt_ModelViewProjectionMatrix * qt_Vertex;
    qt_TexCoord0 = qt_MultiTexCoord0;
}

Shader.h

This file contains multiple classes inside

#ifndef IMGH_COLORMAPSHADER_H
#define IMGH_COLORMAPSHADER_H

#include <QtQuick/QSGMaterial>
#include <QtQuick/QSGTexture>
#include <QtQuick/QSGGeometryNode>
#include <QtQuick/QSGTextureProvider>

/// !!!!!!!!!
/// Custom Material Shader Class declaration
class ColormapShader : public QSGMaterialShader
{
public:
    ColormapShader()
    {
        setShaderFileName(VertexStage, QLatin1String(":/myShader.vert.qsb"));
        setShaderFileName(FragmentStage, QLatin1String(":/myShader.frag.qsb"));
    }
    bool updateUniformData(RenderState &state,
                           QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;

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

/// !!!!!!!!!
/// Custom Material Class declaration
class ColormapMaterial : public QSGMaterial
{
public:
    ColormapMaterial();
    QSGMaterialType *type() const override;
    int compare(const QSGMaterial *other) const override;

    QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
    {
        return new ColormapShader;
    }

    struct {
        QSGTexture *colormap = nullptr;
        QSGTexture *texture = nullptr;
    } state;
};

/// !!!!!!!!!
/// Custom Geometry Node Class declaration
class ColormapNode : public QSGGeometryNode
{
public:
    ColormapNode()
    {
        // initialized the Material object
        m_material = new ColormapMaterial;
        setMaterial(m_material);
        setFlag(OwnsMaterial, true);

        // initialize the geometry object
        QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
        QSGGeometry::updateTexturedRectGeometry(g, QRect(), QRect());
        g->setDrawingMode(GL_TRIANGLE_STRIP);
        setGeometry(g);
        setFlag(OwnsGeometry, true);
    }

    /// request for an update of the geometry of the shader (vertex)
    void setRect(const QRectF &bounds, bool updateUI = true)
    {
        QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1));

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

    /// request for an update of the colormap sampler inside the Shader
    void setColormap(QSGTexture* colormap, bool updateUI = true)
    {
        if(updateTexture(m_colormap, colormap))
            m_material->state.colormap = colormap;

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

    /// request for an update of the texture sampler inside the Shader
    void setTexture(QSGTexture* texture, bool updateUI = true)
    {
        if(updateTexture(m_texture, texture))
            m_material->state.texture = texture;

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

    ColormapMaterial* colormapMaterial() const { return m_material; }
private:
    ColormapMaterial* m_material = nullptr;
    QSGTexture* m_colormap = nullptr;
    QSGTexture* m_texture = nullptr;

    /// Process to update a texture from a shader
    /// Automatically delete the old texture to prevent memory leaks
    bool updateTexture(QSGTexture*& old, QSGTexture*& newOne)
    {
        if(newOne == old)
            return false;

        if(old != nullptr)
            delete old;

        old = newOne;
        if(old)
            old->setFiltering(QSGTexture::None);
        return true;
    }
};

#endif // IMGH_COLORMAPSHADER_H

Shader.cpp

#include "imgh_colormapshader.h"

ColormapMaterial::ColormapMaterial()
{
    setFlag(Blending);
}

QSGMaterialType *ColormapMaterial::type() const
{
    static QSGMaterialType type;
    return &type;
}

int ColormapMaterial::compare(const QSGMaterial *o) const
{
    Q_ASSERT(o && type() == o->type());
    const auto *other = static_cast<const ColormapMaterial *>(o);

    if (!state.colormap || !other->state.colormap)
        return state.colormap ? 1 : -1;

    if (!state.texture || !other->state.texture)
        return state.texture ? -1 : 1;

    qint64 diff = state.colormap->comparisonKey() - other->state.colormap->comparisonKey();
    if (diff != 0)
        return diff < 0 ? -1 : 1;

    diff = state.texture->comparisonKey() - other->state.texture->comparisonKey();
    return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
}

bool ColormapShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
    bool changed = false;
    QByteArray *buf = state.uniformData();
    qDebug() << "Shader's buffer size:" << buf->size(); //TODO: rm after debug
    Q_ASSERT(buf->size() >= 68);

    if (state.isMatrixDirty()) {
        const QMatrix4x4 m = state.combinedMatrix();
        memcpy(buf->data(), m.constData(), 64);
        changed = true;
    }

    if (state.isOpacityDirty()) {
        const float opacity = state.opacity();
        memcpy(buf->data() + 64, &opacity, 4);
        changed = true;
    }

    return changed;
}

void ColormapShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
                                        QSGMaterial *newMaterial, QSGMaterial *)
{
    Q_UNUSED(state);
    qDebug() << "Updating sampled image nb:" << binding; //TODO: rm after debug

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

Custom QQuickItem

op_imageviewer.h

#ifndef OP_IMAGEVIEWER_H
#define OP_IMAGEVIEWER_H

#include <QQuickWindow>
#include <QQuickItem>
#include <QSGGeometryNode>
#include <QSGTexture>
#include <QQuickWindow>
#include <QImage>
#include <QColor>
#include <QThread>
#include <QOpenGLFunctions>

#include <Shaders/imgh_colormapshader.h>

class OP_ImageViewer : public QQuickItem
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QImage image WRITE setImage)
    Q_PROPERTY(QImage colormap READ colormap WRITE setColormap NOTIFY colormapChanged)

public:

    OP_ImageViewer(QQuickItem *parent = nullptr);

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

        m_sourceImage = image;
        m_sourceImageChanged = true;
        update();
    }

    QImage colormap() const { return m_colormapImage; }
    void setColormap(QImage colormap) {
        if (colormap.isNull())
            return; // No changes or null image doesn't update the item

        m_colormapImage = colormap;
        m_colormapImageChanged = true;
        emit colormapChanged();
        update();

    }

protected:
    QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override;
    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;

    void InitializeNewNode(ColormapNode *& node);
    void updateVertex(ColormapNode *& node);
    void updateColormap(ColormapNode *& node);
    void updateTexture(ColormapNode *& node);

private:
    QImage m_sourceImage;
    QImage m_colormapImage;

    // updaters
    bool m_geometryChanged = false;
    bool m_colormapImageChanged = false;
    bool m_sourceImageChanged = false;

signals:
    void colormapChanged();
};

#endif // OP_IMAGEVIEWER_H

op_imageviewer.cpp

#include "op_imageviewer.h"

OP_ImageViewer::OP_ImageViewer(QQuickItem *parent) :
    QQuickItem(parent)
{
    setFlag(ItemHasContents, true);
    m_colormapImage = QImage(2 ,1 ,QImage::Format_ARGB32_Premultiplied);
    m_colormapImage.setPixel(0 ,0 ,qRgb(0,0,0));
    m_colormapImage.setPixel(1 ,0 ,qRgb(255,255,255));
    m_sourceImage = QImage(1, 1, QImage::Format_Grayscale16);
}

QSGNode *OP_ImageViewer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    ColormapNode* node = nullptr;
    if(oldNode == nullptr)
        InitializeNewNode(node);
    else
        node = static_cast<ColormapNode*>(oldNode);

    if(boundingRect().isEmpty())
    {
        delete node;
        return nullptr;
    }

    if(m_geometryChanged)
        updateVertex(node);
    if(m_colormapImageChanged)
        updateColormap(node);
    if(m_sourceImageChanged)
        updateTexture(node);

    return node;
}

void OP_ImageViewer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    if(newGeometry == oldGeometry)
        return;

    m_geometryChanged = true;
    update();
    QQuickItem::geometryChange(newGeometry, oldGeometry);
}

void OP_ImageViewer::InitializeNewNode(ColormapNode *& node)
{
    // Create Node
    // The node instances itself the Material and the Geometry
    node = new ColormapNode();

    // set default values
    updateVertex(node);
    updateColormap(node);
    updateTexture(node);
}

void OP_ImageViewer::updateVertex(ColormapNode *&node)
{
    node->setRect(boundingRect());
    m_geometryChanged = false;
}

void OP_ImageViewer::updateColormap(ColormapNode *&node)
{
    node->setColormap(window()->createTextureFromImage(m_colormapImage));
    m_colormapImageChanged = false;
}

void OP_ImageViewer::updateTexture(ColormapNode *&node)
{
    // this test doesn't work
    // auto t = QImage(m_sourceImage.size(), QImage::Format_RGBA8888);
    // t.fill(QColor("white"));
    // node->setTexture(window()->createTextureFromImage(t, QQuickWindow::TextureIsOpaque));


    node->setTexture(window()->createTextureFromImage(m_sourceImage, QQuickWindow::TextureIsOpaque));
    m_sourceImageChanged = false;

}

Tries

  1. As my m_sourceImage is a Grayscale16 format image, I tried to send a simple ARGB32 QImage instead, but the result was the same as for the Grayscale16 one. (see op_imageviewer.cpp)
  2. I added qDebug() calls to log the shader’s update, and the shader is updated and passes over both overridden methods: updateUniformData and updateSampledImage (see Shader.h)
  3. I change the fragment shader implementation to only send a simple vec4 color, which works, then I indexed the vec4 with the vertex in vec2: vTexCoord and the QQuickItem is correctly colored depending on the pixel’s position. BUT, when I try to use both or only one of my sampler2D object, the result is a transparent item. Also, remove the alpha from the texture(...) and apply handly a value of 1, will colorized the QQuickItem in a fully black square.

I’ve never asked any question on a forum to have help, as looking for already existing topics was enough to allow me to know what was wrong with my code, or gave me some direction to know the issue. But I don’t know why I can’t find any reason about this kind of issue. I’m an OpenGL beginner as I started to learn it last week, so I hope I’m just making junior’s mistakes ^^’.

Sorry for all this code/text, but I wanted to give you the most context about my problem and not just ‘It doesn’t work !!!’.

Thank you in advance to read it and maybe to give me some tips. 🙂

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