You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

树莓派4平台Qt应用:如何在QQuickPaintedItem中通过OpenGL与GPU绘制QImage并实现QImage转OpenGL纹理绘制

嘿,这个场景我刚好折腾过!在树莓派4上用Qt做GPU加速的QImage绘制,确实能让你的应用流畅不少,特别是画面需要频繁更新的时候。下面给你两种可行的方案,分别对应不同的需求:

方案一:改用QQuickFramebufferObject(推荐)

QQuickPaintedItem本身是为CPU绘制设计的,而QQuickFramebufferObject是Qt官方推荐的GPU加速绘制组件,直接在GPU帧缓冲上操作,能大幅减少CPU与GPU之间的数据拷贝,性能提升非常明显。

实现步骤

  1. 创建继承自QQuickFramebufferObject的自定义类,用来管理QImage数据;
  2. 实现配套的Renderer类,负责OpenGL纹理初始化、数据上传和绘制逻辑;
  3. 在QML中直接使用这个自定义组件。

核心代码示例

// FramebufferItem.h
#include <QQuickFramebufferObject>
#include <QImage>

class FramebufferItem : public QQuickFramebufferObject {
    Q_OBJECT
    Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
    Renderer* createRenderer() const override;
    
    QImage image() const { return m_image; }
    void setImage(const QImage& image) {
        if (m_image != image) {
            m_image = image;
            emit imageChanged();
            update();
        }
    }

signals:
    void imageChanged();

private:
    QImage m_image;
};

// Renderer实现
class FramebufferRenderer : public QQuickFramebufferObject::Renderer {
public:
    FramebufferRenderer() {
        // 初始化简单的纹理绘制着色器
        m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
            "attribute vec2 a_pos;\n"
            "attribute vec2 a_texCoord;\n"
            "varying vec2 v_texCoord;\n"
            "void main() {\n"
            "    gl_Position = vec4(a_pos, 0.0, 1.0);\n"
            "    v_texCoord = a_texCoord;\n"
            "}\n");
        m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
            "uniform sampler2D u_texture;\n"
            "varying vec2 v_texCoord;\n"
            "void main() {\n"
            "    gl_FragColor = texture2D(u_texture, v_texCoord);\n"
            "}\n");
        m_program.link();
        
        // 获取着色器属性和 uniform 位置
        m_posAttr = m_program.attributeLocation("a_pos");
        m_texCoordAttr = m_program.attributeLocation("a_texCoord");
        m_textureUniform = m_program.uniformLocation("u_texture");

        // 创建OpenGL纹理
        glGenTextures(1, &m_textureId);
        glBindTexture(GL_TEXTURE_2D, m_textureId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    ~FramebufferRenderer() {
        glDeleteTextures(1, &m_textureId);
    }

    void render() override {
        glClear(GL_COLOR_BUFFER_BIT);
        m_program.bind();

        // 获取最新的QImage并更新纹理
        auto item = static_cast<const FramebufferItem*>(framebufferObject());
        const QImage& image = item->image();
        if (!image.isNull()) {
            QImage glImage = image.convertToFormat(QImage::Format_RGBA8888).mirrored(); // 翻转Y轴适配OpenGL纹理坐标系
            glBindTexture(GL_TEXTURE_2D, m_textureId);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glImage.width(), glImage.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, glImage.bits());
        }

        // 绘制全屏四边形
        const float vertices[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
        const float texCoords[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
        
        glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
        glVertexAttribPointer(m_texCoordAttr, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
        glEnableVertexAttribArray(m_posAttr);
        glEnableVertexAttribArray(m_texCoordAttr);

        glUniform1i(m_textureUniform, 0);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

        m_program.release();
    }

    QOpenGLFramebufferObject* createFramebufferObject(const QSize& size) override {
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
        return new QOpenGLFramebufferObject(size, format);
    }

private:
    QOpenGLShaderProgram m_program;
    GLuint m_textureId = 0;
    int m_posAttr = -1;
    int m_texCoordAttr = -1;
    int m_textureUniform = -1;
};

QQuickFramebufferObject::Renderer* FramebufferItem::createRenderer() const {
    return new FramebufferRenderer();
}

QML中使用

import QtQuick 2.15
import YourCustomModule 1.0

FramebufferItem {
    width: 640
    height: 480
    image: yourQImageSource // 绑定你的QImage数据源
}

方案二:在QQuickPaintedItem中直接集成OpenGL

如果你不想改动现有类结构,也可以在QQuickPaintedItempaint方法中切换到OpenGL上下文进行绘制,不过这种方式因为涉及CPU/GPU上下文切换,性能不如方案一。

核心代码示例

class GLPaintedItem : public QQuickPaintedItem {
    Q_OBJECT
    Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
    GLPaintedItem(QQuickItem* parent = nullptr) : QQuickPaintedItem(parent) {
        setRenderTarget(QQuickPaintedItem::FramebufferObject); // 启用离屏渲染避免冲突
    }

    QImage image() const { return m_image; }
    void setImage(const QImage& image) {
        if (m_image != image) {
            m_image = image;
            m_needsTextureUpdate = true;
            emit imageChanged();
            update();
        }
    }

    void paint(QPainter* painter) override {
        painter->beginNativePainting(); // 进入OpenGL上下文
        QOpenGLContext* ctx = QOpenGLContext::currentContext();
        if (!ctx) {
            painter->endNativePainting();
            return;
        }

        // 初始化纹理(仅第一次调用)
        if (m_textureId == 0) {
            glGenTextures(1, &m_textureId);
            glBindTexture(GL_TEXTURE_2D, m_textureId);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        }

        // 更新纹理数据(仅当image变化时)
        if (m_needsTextureUpdate && !m_image.isNull()) {
            QImage glImage = m_image.convertToFormat(QImage::Format_RGBA8888).mirrored();
            glBindTexture(GL_TEXTURE_2D, m_textureId);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glImage.width(), glImage.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, glImage.bits());
            m_needsTextureUpdate = false;
        }

        // 用OpenGL绘制纹理(这里用简化的固定管线示例,推荐用着色器)
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, width(), height(), 0, -1, 1);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_textureId);

        glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex2f(0, 0);
        glTexCoord2f(1, 0); glVertex2f(width(), 0);
        glTexCoord2f(1, 1); glVertex2f(width(), height());
        glTexCoord2f(0, 1); glVertex2f(0, height());
        glEnd();

        glDisable(GL_TEXTURE_2D);
        painter->endNativePainting(); // 回到QPainter上下文
    }

signals:
    void imageChanged();

protected:
    void cleanup() {
        if (m_textureId != 0) {
            glDeleteTextures(1, &m_textureId);
            m_textureId = 0;
        }
    }

private:
    QImage m_image;
    GLuint m_textureId = 0;
    bool m_needsTextureUpdate = false;
};

树莓派上的关键注意事项

  • 确保Qt支持OpenGL ES:树莓派4用的是OpenGL ES 3.0,官方Qt for Raspberry Pi已经默认配置,自己编译的话要加-opengl es3参数;
  • 纹理更新优化:只在QImage内容变化时上传纹理数据,不要每次paint都重复操作;
  • 格式与坐标系适配:QImage默认是RGBA8888以外的格式,需要转换,同时要翻转Y轴适配OpenGL纹理的坐标系。

内容的提问来源于stack exchange,提问作者Dehghannn

火山引擎 最新活动