树莓派4平台Qt应用:如何在QQuickPaintedItem中通过OpenGL与GPU绘制QImage并实现QImage转OpenGL纹理绘制
嘿,这个场景我刚好折腾过!在树莓派4上用Qt做GPU加速的QImage绘制,确实能让你的应用流畅不少,特别是画面需要频繁更新的时候。下面给你两种可行的方案,分别对应不同的需求:
方案一:改用QQuickFramebufferObject(推荐)
QQuickPaintedItem本身是为CPU绘制设计的,而QQuickFramebufferObject是Qt官方推荐的GPU加速绘制组件,直接在GPU帧缓冲上操作,能大幅减少CPU与GPU之间的数据拷贝,性能提升非常明显。
实现步骤
- 创建继承自
QQuickFramebufferObject的自定义类,用来管理QImage数据; - 实现配套的
Renderer类,负责OpenGL纹理初始化、数据上传和绘制逻辑; - 在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
如果你不想改动现有类结构,也可以在QQuickPaintedItem的paint方法中切换到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




