Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to smooth the painting of a custom QML element?

I try now to create custom QML element, derived from QQuickItem. So I overrided QQuickItem::updatePaintNode and want now do draw a line. My code:

QSGNode *StrikeLine::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
    QSGGeometryNode *node = 0;

    QSGGeometry *geometry;
    QSGFlatColorMaterial *material;
    node = static_cast<QSGGeometryNode *>(oldNode);
    if(!node) {
        node = new QSGGeometryNode;
        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
        geometry->setDrawingMode(GL_LINES);
        geometry->setLineWidth(3);
        material = new QSGFlatColorMaterial;
        material->setColor(QColor(255, 0, 0));
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);
        getColor();
    } else {
        geometry = node->geometry();
        material = static_cast<QSGFlatColorMaterial *>(node->material());
    }
    geometry->vertexDataAsPoint2D()[0].set(p_startPoint.x(), p_startPoint.y());
    geometry->vertexDataAsPoint2D()[1].set(p_endPoint.x(), p_endPoint.y());
    material->setColor(getColor());
    node->markDirty(QSGNode::DirtyGeometry);

    return node;
}

But my line looks so ugly. The edges are rough and it looks like DOS graphics at all. So my question - how can I apply smooth painting? I now it may be some shader or something but I cannot find any documentation.

like image 799
folibis Avatar asked Jan 24 '15 12:01

folibis


1 Answers

The scene graph supports two types of antialiasing. Primitives such as rectangles and images will be antialiased by adding more vertices along the edge of the primitives so that the edges fade to transparent. This method called vertex antialiasing. If you requests a multisampled OpenGL context, the scene graph will prefer multisample based antialiasing (MSAA).

Vertex antialiasing can produce seams between edges of adjacent primitives, even when the two edges are mathmatically the same. Multisample antialiasing does not.

Multisample Antialiasing

Multisample antialiasing is a hardware feature where the hardware calculates a coverage value per pixel in the primitive. Some hardware can multisample at a very low cost, while other hardware may need both more memory and more GPU cycles to render a frame.

To enable multisample antialiasing you should set QSurfaceFormat with samples greater than 0 using QQuickWindow::setFormat()

QQuickView view;
QSurfaceFormat format = view.format();
format.setSamples(16);
view.setFormat(format);
view.show();

Vertex Antialiasing

Vertex antialiasing can be enabled and disabled on a per-item basis using the Item::antialiasing property. It will work regardless of what the underlying hardware supports and produces higher quality antialiasing, both for normally rendered primitives and also for primitives captured into framebuffer objects.

The downside to using vertex antialiasing is that each primitive with antialiasing enabled will have to be blended. In terms of batching, this means that the renderer needs to do more work to figure out if the primitive can be batched or not and due to overlaps with other elements in the scene, it may also result in less batching, which could impact performance.


To apply vertex antialiasing to custom QML element, derived from QQuickItem, follow next steps:

1) Create custom material and OpenGL shader program.

smoothcolormaterial.h

#include <QSGMaterial>
#include <QSGMaterialShader>

//----------------------------------------------------------------------

class QSGSmoothColorMaterial : public QSGMaterial
{
public:
    QSGSmoothColorMaterial();
    int compare(const QSGMaterial *other) const;
protected:
    virtual QSGMaterialType *type() const;
    virtual QSGMaterialShader *createShader() const;
};

//----------------------------------------------------------------------

class QSGSmoothColorMaterialShader : public QSGMaterialShader
{
public:
    QSGSmoothColorMaterialShader();
    virtual void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect);
    virtual char const *const *attributeNames() const;
private:
    void initialize();
    int m_matrixLoc;
    int m_opacityLoc;
    int m_pixelSizeLoc;
};

smoothcolormaterial.cpp

QSGSmoothColorMaterial::QSGSmoothColorMaterial()
{
    setFlag(RequiresFullMatrixExceptTranslate, true);
    setFlag(Blending, true);
}

int QSGSmoothColorMaterial::compare(const QSGMaterial *other) const
{
    Q_UNUSED(other)
    return 0;
}

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

QSGMaterialShader *QSGSmoothColorMaterial::createShader() const
{
    return new QSGSmoothColorMaterialShader();
}

//----------------------------------------------------------------------

QSGSmoothColorMaterialShader::QSGSmoothColorMaterialShader()
    : QSGMaterialShader()
{
    setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/shaders/smoothcolor.vert"));
    setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/shaders/smoothcolor.frag"));
}

void QSGSmoothColorMaterialShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
    Q_UNUSED(newEffect)

    if (state.isOpacityDirty())
        program()->setUniformValue(m_opacityLoc, state.opacity());

    if (state.isMatrixDirty())
        program()->setUniformValue(m_matrixLoc, state.combinedMatrix());

    if (oldEffect == 0) {
        // The viewport is constant, so set the pixel size uniform only once.
        QRect r = state.viewportRect();
        program()->setUniformValue(m_pixelSizeLoc, 2.0f / r.width(), 2.0f / r.height());
    }
}

const char * const *QSGSmoothColorMaterialShader::attributeNames() const
{
    static char const *const attributes[] = {
        "vertex",
        "vertexColor",
        "vertexOffset",
        0
    };
    return attributes;
}

void QSGSmoothColorMaterialShader::initialize()
{
    m_matrixLoc = program()->uniformLocation("matrix");
    m_opacityLoc = program()->uniformLocation("opacity");
    m_pixelSizeLoc = program()->uniformLocation("pixelSize");
}

Fragment Shader

varying lowp vec4 color;

void main()
{
    gl_FragColor = color;
}

Vertex Shader

uniform highp vec2 pixelSize;
uniform highp mat4 matrix;
uniform lowp float opacity;

attribute highp vec4 vertex;
attribute lowp vec4 vertexColor;
attribute highp vec2 vertexOffset;

varying lowp vec4 color;

void main()
{
    highp vec4 pos = matrix * vertex;
    gl_Position = pos;

    if (vertexOffset.x != 0.) {
        highp vec4 delta = matrix[0] * vertexOffset.x;
        highp vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
        highp vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
        dir -= ndir * delta.w * pos.w;
        highp float numerator = dot(dir, ndir * pos.w * pos.w);
        highp float scale = 0.0;
        if (numerator < 0.0)
            scale = 1.0;
        else
            scale = min(1.0, numerator / dot(dir, dir));
        gl_Position += scale * delta;
    }

    if (vertexOffset.y != 0.) {
        highp vec4 delta = matrix[1] * vertexOffset.y;
        highp vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
        highp vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
        dir -= ndir * delta.w * pos.w;
        highp float numerator = dot(dir, ndir * pos.w * pos.w);
        highp float scale = 0.0;
        if (numerator < 0.0)
            scale = 1.0;
        else
            scale = min(1.0, numerator / dot(dir, dir));
        gl_Position += scale * delta;
    }

    color = vertexColor * opacity;
}

2) Create custom AttributeSet for QSGGeometry.

myquickitem.cpp

namespace
{
    struct Color4ub
    {
        unsigned char r, g, b, a;
    };

    inline Color4ub colorToColor4ub(const QColor &c)
    {
        Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
                           uchar(c.greenF() * c.alphaF() * 255),
                           uchar(c.blueF() * c.alphaF() * 255),
                           uchar(c.alphaF() * 255)
                         };
        return color;
    }

    struct SmoothVertex
    {
        float x, y;
        Color4ub color;
        float dx, dy;
        void set(float nx, float ny, Color4ub ncolor, float ndx, float ndy)
        {
            x = nx; y = ny; color = ncolor;
            dx = ndx; dy = ndy;
        }
    };

    const QSGGeometry::AttributeSet &smoothAttributeSet()
    {
        static QSGGeometry::Attribute data[] = {
            QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true),
            QSGGeometry::Attribute::create(1, 4, GL_UNSIGNED_BYTE, false),
            QSGGeometry::Attribute::create(2, 2, GL_FLOAT, false)
        };
        static QSGGeometry::AttributeSet attrs = { 3, sizeof(SmoothVertex), data };
        return attrs;
    }
}

3) Apply custom material and custom geometry to QSGGeometryNode.

myquickitem.cpp

QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data)
{
     QSGGeometryNode *node = 0;

     QSGGeometry *geometry;
     QSGSmoothColorMaterial *material;
     node = static_cast<QSGGeometryNode *>(oldNode);
     if(!node) {
         node = new QSGGeometryNode;
         geometry = new QSGGeometry(smoothAttributeSet(), 0);
         geometry->setDrawingMode(GL_TRIANGLE_STRIP);
         material = new QSGSmoothColorMaterial();
         node->setGeometry(geometry);
         node->setFlag(QSGNode::OwnsGeometry);
         node->setMaterial(material);
         node->setFlag(QSGNode::OwnsMaterial);
     } else {
         geometry = node->geometry();
         material = static_cast<QSGSmoothColorMaterial *>(node->material());
     }

4) Get pointer to vertex data.

 int vertexStride = geometry->sizeOfVertex();
 int vertexCount = 8;

 geometry->allocate(vertexCount, 0);
 SmoothVertex *smoothVertices = reinterpret_cast<SmoothVertex *>(geometry->vertexData());
 memset(smoothVertices, 0, vertexCount * vertexStride);

5) Set vertex data.

You need 4 points.

 float lineWidth = 4;
 float tlX = 0;   float tlY = 0;               //top-left
 float blX = 0;   float blY = 0 + lineWidth;   //bottom-left
 float trX = 500; float trY = 100;             //top-right
 float brX = 500; float brY = 100 + lineWidth; //bottom-right
 float delta = lineWidth * 0.5f;

 Color4ub fillColor = colorToColor4ub(QColor(255,0,0,255));
 Color4ub transparent = { 0, 0, 0, 0 };

To draw antialiased line you should set 8 vertices to draw 6 triangles(2 for line, 4 for antialiasing). Vertices 0 and 2, 1 and 3, 4 and 6, 5 and 7 have the same coordinates, but different color and opposite vertex offset.

enter image description here

 smoothVertices[0].set(trX, trY, transparent, delta, -delta);
 smoothVertices[1].set(tlX, tlY, transparent, -delta, -delta);

 smoothVertices[2].set(trX, trY, fillColor, -delta, delta);
 smoothVertices[3].set(tlX, tlY, fillColor, delta, delta);
 smoothVertices[4].set(brX, brY, fillColor, -delta, -delta);
 smoothVertices[5].set(blX, blY, fillColor, delta, -delta);

 smoothVertices[6].set(brX, brY, transparent, delta, delta);
 smoothVertices[7].set(blX, blY, transparent, -delta, delta);


 node->markDirty(QSGNode::DirtyGeometry);

 return node;
 }
like image 158
Meefte Avatar answered Oct 05 '22 11:10

Meefte