Search code examples
c++qtqmlqt6

Custom shaders/material not working in a custom QQuickItem object


I have tried following the tutorial given by QT's online documentation (QtQuick Scenegraph CustomMaterial Example), but when I debugged my program, the object does not show, appearing to be transparent. However, when I tried replacing my custom shader class with QSGFlatColorMaterial, and set a color, the object does show without any issues. I double checked my implementation of the tutorial with the example code in the example git repo.

Here is my code in question

/* MapDisplay Class */

MapDisplay::MapDisplay(QQuickItem* parent) : QQuickItem(parent) {
  this->setFlag(ItemHasContents, true);
};

QSGNode* MapDisplay::updatePaintNode(QSGNode* old, UpdatePaintNodeData*) {
  auto* node = static_cast<MapNode*>(old);

  if (!node) {
    node = new MapNode();
  }
  if (this->flag_geo_changed) {
    node->ChangeRectBounds(this->boundingRect());
    this->flag_geo_changed = false;
  }
  const QRectF rect = this->boundingRect();
  auto* vertices = node->geometry()->vertexDataAsPoint2D();
  vertices[0].set(rect.bottomLeft().x(), 1);
  vertices[1].set(200, 0);
  vertices[2].set(0, 200);
  vertices[3].set(200, 200);
  node->markDirty(QSGNode::DirtyGeometry);
  return node;
}

void MapDisplay::geometryChange(const QRectF& new_geo, const QRectF& old_geo) {
  this->flag_geo_changed = false;
  this->update();
  QQuickItem::geometryChange(new_geo, old_geo);
}

/* MapShader Class */

MapShader::MapShader() {
  this->setShaderFileName(VertexStage, ":/geo/shader/map.vert.qsb");
  this->setShaderFileName(FragmentStage, ":/geo/shader/map.frag.qsb");
};

/* MapMaterial Class */

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

int MapMaterial::compare(const QSGMaterial* o) const {
  Q_ASSERT(o && this->type() == o->type());
  const auto* other = static_cast<const MapMaterial*>(o);
  return other == this ? 0 : 1;
}

QSGMaterialShader* MapMaterial::createShader(
    QSGRendererInterface::RenderMode) const {
  return new MapShader();
}

/* MapNode Class */

MapNode::MapNode() {
  // Material
  auto* mat = new MapMaterial();
  this->setMaterial(mat);
  this->setFlag(QSGGeometryNode::OwnsMaterial, true);
  // Geometry
  auto* geo = get_geo_data::GetRectShape();
  this->setGeometry(geo);
  this->setFlag(QSGGeometryNode::OwnsGeometry, true);
}

void MapNode::ChangeRectBounds(const QRectF& bounds) {
  QSGGeometry::updateTexturedRectGeometry(this->geometry(), bounds,
                                          QRectF(0, 0, 0, 0));
  this->markDirty(QSGNode::DirtyGeometry);
}

And here is the link to the example code I cross checked with customitem.cpp

Here are also my shaders too,

#version 460
// map.vert

layout(location = 0) in vec4 vertex_object;
layout(location = 1) in vec2 atex_coord;
layout(location = 0) out vec2 vtex_coord; 

void main() {
  gl_Position = vertex_object;
  vtex_coord = atex_coord;
}
#version 460

// map.frag
layout(location = 0) out vec4 frag_color;

void main() {
  frag_color = vec4(0.0, 1.0, 0.0, 1.0);
}

And here are also some screenshots too:

If I use QSGFlatColorMaterial

If I use my custom material and shaders


Solution

  • Ok, so after prodding through the example repo, I did not multiply the vertex_object in the vert shader with the matrix of the object.

    So after implementing the methods and glsl code from the documention into mine's, I got the shader to show properly. Turns out that I did not properly set the shader position

    map.cpp

    bool MapShader::updateUniformData(RenderState& state, QSGMaterial* new_material,
                                      QSGMaterial* old_material) {
      bool changed = false;
      QByteArray* buf = state.uniformData();
      Q_ASSERT(buf->size() >= 64);
    
      if (state.isMatrixDirty()) {
        const QMatrix4x4 m = state.combinedMatrix();
        std::memcpy(buf->data(), m.constData(), 64);
        changed = true;
      }
      auto* cus_masterial = static_cast<MapMaterial*>(new_material);
      if (old_material != new_material || cus_masterial->uniform.dirty) {
        cus_masterial->uniform.dirty = false;
        changed = true;
      }
      return changed;
    }
    

    map.vert

    #version 460
    
    layout(location = 0) in vec4 vertex_object;
    layout(location = 1) in vec2 atex_coord;
    layout(location = 0) out vec2 vtex_coord; 
    
    layout(std140, binding=0) uniform buf {
      mat4 qt_matrix; // offset 0
    } ubuf;
    
    void main() {
      gl_Position = ubuf.qt_matrix /* Very important */ * vertex_object;
      vtex_coord = atex_coord;
    }