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
- As my
m_sourceImage
is a Grayscale16 format image, I tried to send a simpleARGB32
QImage instead, but the result was the same as for the Grayscale16 one. (see op_imageviewer.cpp) - I added
qDebug()
calls to log the shader’s update, and the shader is updated and passes over both overridden methods:updateUniformData
andupdateSampledImage
(see Shader.h) - I change the fragment shader implementation to only send a simple
vec4
color, which works, then I indexed thevec4
with the vertex invec2
:vTexCoord
and the QQuickItem is correctly colored depending on the pixel’s position. BUT, when I try to use both or only one of mysampler2D
object, the result is a transparent item. Also, remove the alpha from thetexture(...)
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. 🙂
adebono is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.