Qt 5.8中QSGGeometryNode顶点数据重分配绘制异常咨询
首先得说,你遇到的错误连线和分配问题,本质上是动态几何数据更新时的内存管理、图元组织逻辑没跟上,咱们一步步拆解:
一、先揪出核心问题
频繁调用
allocate的隐患
你每次都用m_geometry.allocate(...)来适配数据量变化,这会频繁重新分配内存——虽然StreamPattern是为频繁更新设计的,但反复分配容易导致内存碎片,而且如果分配后没有完全覆盖旧的顶点数据,GPU就会读取到残留的旧顶点,直接引发跨点的错误连线。GL_POLYGON的天然缺陷
GL_POLYGON是用来绘制单个连续多边形的,你把所有点的顶点塞进去,它会默认把第一个点的最后一个顶点和最后一个点的第一个顶点连起来,出现异常连线完全是预期行为,这个图元类型根本不适合你的场景。大数量顶点的异常
当顶点数到25000左右出问题,大概率是因为:要么你没正确设置vertexCount,导致GPU读取了超出当前有效数据范围的内存;要么频繁分配导致内存管理出现异常。
二、处理可变大小数据的正确姿势
1. 选对图元类型,减少顶点开销
你现在用8个三角形(24个顶点)画一个圆,其实可以优化成Triangle Fan:每个圆只需要1个中心顶点+8个边缘顶点(共9个顶点),用QSGGeometry::DrawTriangleFan绘制,既能减少75%的顶点数,还能避免手动拼接三角形的麻烦。如果坚持用DrawTriangles,也要保证每个圆的24个顶点是连续的,且总顶点数是3的倍数。
2. 优化内存分配策略:预分配+动态调整
别每次数据变化就调用allocate,建议:
- 初始化时预分配足够大的缓冲区(比如预估最大支持30000个点的顶点数),避免频繁内存申请;
- 数据更新时,只通过
m_geometry->setVertexCount(实际需要的顶点总数)来控制绘制范围,不需要重新分配; - 只有当数据量超过预分配的缓冲区大小时,再调用
allocate扩容。
示例代码思路:
// 初始化Geometry(假设用带颜色的2D顶点) m_geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0); m_geometry->setVertexDataPattern(QSGGeometry::StreamPattern); // 预分配能容纳30000个圆的顶点(每个圆9个顶点) const int MAX_POINTS = 30000; m_geometry->allocate(MAX_POINTS * 9); // 数据更新时的逻辑 void updatePoints(const QList<PointData>& pts) { const int PER_CIRCLE_VERTS = 9; int totalVerts = pts.size() * PER_CIRCLE_VERTS; // 如果当前数据量超过预分配,再扩容 if (totalVerts > m_geometry->vertexCount()) { m_geometry->allocate(totalVerts); } // 设置实际要绘制的顶点数 m_geometry->setVertexCount(totalVerts); // 填充顶点数据 auto* vertices = static_cast<QSGGeometry::ColoredPoint2D*>(m_geometry->vertexData()); int idx = 0; const float radius = 5.0f; // 你的圆半径 for (const auto& pt : pts) { // 填充中心顶点 vertices[idx].x = pt.x(); vertices[idx].y = pt.y(); vertices[idx].r = pt.color().redF(); vertices[idx].g = pt.color().greenF(); vertices[idx].b = pt.color().blueF(); vertices[idx].a = pt.color().alphaF(); idx++; // 填充8个边缘顶点 for (int i = 0; i < 8; ++i) { float angle = 2 * M_PI * i / 8; vertices[idx].x = pt.x() + radius * cos(angle); vertices[idx].y = pt.y() + radius * sin(angle); vertices[idx].r = pt.color().redF(); vertices[idx].g = pt.color().greenF(); vertices[idx].b = pt.color().blueF(); vertices[idx].a = pt.color().alphaF(); idx++; } } // 必须标记Geometry脏,通知场景图更新 markDirty(QSGNode::DirtyGeometry); }
3. 用索引缓冲区进一步优化(可选)
如果你的圆都是相同的顶点分布(只是位置和颜色不同),还可以用共享顶点+索引缓冲区:
- 只存一个圆的9个顶点模板;
- 每个实际的圆只存中心位置和颜色,通过索引重复使用模板顶点;
- 这种方式能把内存占用降到极致,尤其适合超大量点的场景。
4. 关键细节:确保线程安全
所有Geometry的更新操作,必须在QSGNode的updatePaintNode方法里,或者在主线程调用(因为QSG的渲染线程和主线程是分离的),避免多线程操作导致的数据不一致。
三、验证与排查
如果还是出现异常:
- 检查总顶点数是否符合图元要求:
DrawTriangles必须是3的倍数,DrawTriangleFan每个圆是9个顶点,总数是9的倍数; - 打印每次更新后的
vertexCount和实际数据量的对应关系,确认没有计算错误; - 用
QSGGeometry::vertexData()读取最后几个顶点的数据,看是否是你预期的最新点,排查是否有旧数据残留。
内容的提问来源于stack exchange,提问作者kmx78

