From 2b28e1f18c3d9f8f7c116669049aaffaa220804a Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Tue, 9 Aug 2011 14:50:07 +0200 Subject: [PATCH] Implement VBO caching of larger geometries Change-Id: If87b70b191a06448287b47d252a288b441af6302 Reviewed-on: http://codereview.qt.nokia.com/2784 Reviewed-by: Qt Sanity Bot Reviewed-by: Yoann Lopes --- src/declarative/items/qsgtextnode.cpp | 13 ++- .../scenegraph/coreapi/qsgdefaultrenderer.cpp | 3 +- src/declarative/scenegraph/coreapi/qsggeometry.cpp | 113 ++++++++++++ src/declarative/scenegraph/coreapi/qsggeometry.h | 28 +++- src/declarative/scenegraph/coreapi/qsggeometry_p.h | 32 ++++ src/declarative/scenegraph/coreapi/qsgrenderer.cpp | 187 ++++++++++++++++--- src/declarative/scenegraph/coreapi/qsgrenderer_p.h | 12 +- src/declarative/scenegraph/scenegraph.pri | 4 +- 8 files changed, 351 insertions(+), 41 deletions(-) create mode 100644 src/declarative/scenegraph/coreapi/qsggeometry_p.h diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp index d36db1b..03a558f 100644 --- a/src/declarative/items/qsgtextnode.cpp +++ b/src/declarative/items/qsgtextnode.cpp @@ -166,9 +166,20 @@ QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &g node->update(); - if (node != prevNode) + // A new node, add it to the graph. + if (node != prevNode) { appendChildNode(node); + /* We flag the geometry as static, but we never call markVertexDataDirty + or markIndexDataDirty on them. This is because all text nodes are + discarded when a change occurs. If we start appending/removing from + existing geometry, then we also need to start marking the geometry as + dirty. + */ + node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern); + node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern); + } + return node; } diff --git a/src/declarative/scenegraph/coreapi/qsgdefaultrenderer.cpp b/src/declarative/scenegraph/coreapi/qsgdefaultrenderer.cpp index 8af626e..ce05e76 100644 --- a/src/declarative/scenegraph/coreapi/qsgdefaultrenderer.cpp +++ b/src/declarative/scenegraph/coreapi/qsgdefaultrenderer.cpp @@ -510,8 +510,7 @@ void QSGDefaultRenderer::renderNodes(const QDataBuffer &list) //glDepthRange((geomNode->renderOrder() + 0.1) * scale, (geomNode->renderOrder() + 0.9) * scale); const QSGGeometry *g = geomNode->geometry(); - bindGeometry(program, g); - draw(geomNode); + draw(program, g); #ifdef RENDERER_DEBUG geometryNodesDrawn++; diff --git a/src/declarative/scenegraph/coreapi/qsggeometry.cpp b/src/declarative/scenegraph/coreapi/qsggeometry.cpp index 71b5cb6..6b622af 100644 --- a/src/declarative/scenegraph/coreapi/qsggeometry.cpp +++ b/src/declarative/scenegraph/coreapi/qsggeometry.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "qsggeometry.h" +#include "qsggeometry_p.h" QT_BEGIN_NAMESPACE @@ -122,7 +123,10 @@ QSGGeometry::QSGGeometry(const QSGGeometry::AttributeSet &attributes, , m_attributes(attributes) , m_data(0) , m_index_data_offset(-1) + , m_server_data(0) , m_owns_data(false) + , m_index_usage_pattern(AlwaysUploadPattern) + , m_vertex_usage_pattern(AlwaysUploadPattern) { Q_ASSERT(m_attributes.count > 0); Q_ASSERT(m_attributes.stride > 0); @@ -136,6 +140,9 @@ QSGGeometry::~QSGGeometry() { if (m_owns_data) qFree(m_data); + + if (m_server_data) + delete m_server_data; } /*! @@ -250,6 +257,15 @@ void QSGGeometry::allocate(int vertexCount, int indexCount) m_owns_data = true; } + // If we have associated vbo data we could potentially crash later if + // the old buffers are used with the new vertex and index count, so we force + // an update in the renderer in that case. This is really the users responsibility + // but it is cheap for us to enforce this, so why not... + if (m_server_data) { + markIndexDataDirty(); + markVertexDataDirty(); + } + } /*! @@ -307,4 +323,101 @@ void QSGGeometry::updateTexturedRectGeometry(QSGGeometry *g, const QRectF &rect, v[3].ty = textureRect.bottom(); } + + +/*! + \enum QSGGeometry::DataPattern + + The DataPattern enum is used to specify the use pattern for the vertex + and index data in a geometry object. + + \value AlwaysUploadPattern The data is always uploaded. This means that + the user does not need to explicitly mark index and vertex data as + dirty after changing it. This is the default. + + \value DynamicPattern The data is modified repeatedly and drawn many times. + This is a hint that may provide better performance. When set + the user must make sure to mark the data as dirty after changing it. + + \value StaticPattern The data is modified once and drawn many times. This is + a hint that may provide better performance. When set the user must make sure + to mark the data as dirty after changing it. + */ + + +/*! + \fn QSGGeometry::DataPattern QSGGeometry::indexDataPattern() const + + Returns the usage pattern for indices in this geometry. The default + pattern is AlwaysUploadPattern. + */ + +/*! + Sets the usage pattern for indices to \a p. + + The default is AlwaysUploadPattern. When set to anything other than + the default, the user must call markIndexDataDirty() after changing + the index data. + */ + +void QSGGeometry::setIndexDataPattern(DataPattern p) +{ + m_index_usage_pattern = p; +} + + + + +/*! + \fn QSGGeometry::DataPattern QSGGeometry::vertexDataPattern() const + + Returns the usage pattern for vertices in this geometry. The default + pattern is AlwaysUploadPattern. + */ + +/*! + Sets the usage pattern for vertices to \a p. + + The default is AlwaysUploadPattern. When set to anything other than + the default, the user must call markVertexDataDirty() after changing + the vertex data. + */ + +void QSGGeometry::setVertexDataPattern(DataPattern p) +{ + m_vertex_usage_pattern = p; +} + + + + +/*! + Mark that the vertices in this geometry has changed and must be uploaded + again. + + This function only has an effect when the usage pattern for vertices is + StaticData and the renderer that renders this geometry uploads the geometry + into Vertex Buffer Objects (VBOs). + */ +void QSGGeometry::markIndexDataDirty() +{ + m_dirty_index_data = true; +} + + + +/*! + Mark that the vertices in this geometry has changed and must be uploaded + again. + + This function only has an effect when the usage pattern for vertices is + StaticData and the renderer that renders this geometry uploads the geometry + into Vertex Buffer Objects (VBOs). + */ +void QSGGeometry::markVertexDataDirty() +{ + m_dirty_vertex_data = true; +} + + QT_END_NAMESPACE diff --git a/src/declarative/scenegraph/coreapi/qsggeometry.h b/src/declarative/scenegraph/coreapi/qsggeometry.h index 4325030..f99eee3 100644 --- a/src/declarative/scenegraph/coreapi/qsggeometry.h +++ b/src/declarative/scenegraph/coreapi/qsggeometry.h @@ -50,6 +50,8 @@ QT_BEGIN_NAMESPACE QT_MODULE(Declarative) +class QSGGeometryData; + class Q_DECLARATIVE_EXPORT QSGGeometry { public: @@ -92,6 +94,13 @@ public: static const AttributeSet &defaultAttributes_TexturedPoint2D(); static const AttributeSet &defaultAttributes_ColoredPoint2D(); + enum DataPattern { + AlwaysUploadPattern = 0, + StreamPattern = 1, + DynamicPattern = 2, + StaticPattern = 3 + }; + QSGGeometry(const QSGGeometry::AttributeSet &attribs, int vertexCount, int indexCount = 0, @@ -134,7 +143,18 @@ public: static void updateRectGeometry(QSGGeometry *g, const QRectF &rect); static void updateTexturedRectGeometry(QSGGeometry *g, const QRectF &rect, const QRectF &sourceRect); + void setIndexDataPattern(DataPattern p); + DataPattern indexDataPattern() const { return (DataPattern) m_index_usage_pattern; } + + void setVertexDataPattern(DataPattern p); + DataPattern vertexDataPattern() const { return (DataPattern) m_vertex_usage_pattern; } + + void markIndexDataDirty(); + void markVertexDataDirty(); + private: + friend class QSGGeometryData; + int m_drawing_mode; int m_vertex_count; int m_index_count; @@ -143,10 +163,14 @@ private: void *m_data; int m_index_data_offset; - void *m_reserved_pointer; + QSGGeometryData *m_server_data; uint m_owns_data : 1; - uint m_reserved_bits : 31; + uint m_index_usage_pattern : 2; + uint m_vertex_usage_pattern : 2; + uint m_dirty_index_data : 1; + uint m_dirty_vertex_data : 1; + uint m_reserved_bits : 27; float m_prealloc[16]; }; diff --git a/src/declarative/scenegraph/coreapi/qsggeometry_p.h b/src/declarative/scenegraph/coreapi/qsggeometry_p.h new file mode 100644 index 0000000..2fba155 --- /dev/null +++ b/src/declarative/scenegraph/coreapi/qsggeometry_p.h @@ -0,0 +1,32 @@ +#ifndef QSGGEOMETRY_P_H +#define QSGGEOMETRY_P_H + +#include "qsggeometry.h" + +QT_BEGIN_NAMESPACE + +class QSGGeometryData +{ +public: + virtual ~QSGGeometryData() {} + + static inline QSGGeometryData *data(const QSGGeometry *g) { + return g->m_server_data; + } + + static inline void install(const QSGGeometry *g, QSGGeometryData *data) { + Q_ASSERT(!g->m_server_data); + const_cast(g)->m_server_data = data; + } + + static bool inline hasDirtyVertexData(const QSGGeometry *g) { return g->m_dirty_vertex_data; } + static void inline clearDirtyVertexData(const QSGGeometry *g) { const_cast(g)->m_dirty_vertex_data = false; } + + static bool inline hasDirtyIndexData(const QSGGeometry *g) { return g->m_dirty_vertex_data; } + static void inline clearDirtyIndexData(const QSGGeometry *g) { const_cast(g)->m_dirty_index_data = false; } + +}; + +QT_END_NAMESPACE + +#endif // QSGGEOMETRY_P_H diff --git a/src/declarative/scenegraph/coreapi/qsgrenderer.cpp b/src/declarative/scenegraph/coreapi/qsgrenderer.cpp index 78551b1..5a137ad 100644 --- a/src/declarative/scenegraph/coreapi/qsgrenderer.cpp +++ b/src/declarative/scenegraph/coreapi/qsgrenderer.cpp @@ -43,6 +43,7 @@ #include "qsgnode.h" #include "qsgmaterial.h" #include "qsgnodeupdater_p.h" +#include "qsggeometry_p.h" #include "private/qsgadaptationlayer_p.h" @@ -130,6 +131,8 @@ QSGRenderer::QSGRenderer(QSGContext *context) , m_changed_emitted(false) , m_mirrored(false) , m_is_rendering(false) + , m_vertex_buffer_bound(false) + , m_index_buffer_bound(false) { initializeGLFunctions(); } @@ -254,6 +257,16 @@ void QSGRenderer::renderScene(const QSGBindable &bindable) m_changed_emitted = false; m_bindable = 0; + if (m_vertex_buffer_bound) { + glBindBuffer(GL_ARRAY_BUFFER, 0); + m_vertex_buffer_bound = false; + } + + if (m_index_buffer_bound) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + m_index_buffer_bound = false; + } + #ifdef QSG_RENDERER_TIMING printf(" - Breakdown of frametime: preprocess=%d, updates=%d, binding=%d, render=%d, total=%d\n", preprocessTime, @@ -459,14 +472,17 @@ QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip) glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass - const QSGGeometry *geometry = clip->geometry(); - Q_ASSERT(geometry->attributeCount() > 0); - const QSGGeometry::Attribute *a = geometry->attributes(); - - glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, geometry->stride(), geometry->vertexData()); + const QSGGeometry *g = clip->geometry(); + Q_ASSERT(g->attributeCount() > 0); + const QSGGeometry::Attribute *a = g->attributes(); + glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, g->stride(), g->vertexData()); m_clip_program.setUniformValue(m_clip_matrix_id, m); - draw(clip); + if (g->indexCount()) { + glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); + } else { + glDrawArrays(g->drawingMode(), 0, g->vertexCount()); + } ++clipDepth; } @@ -493,25 +509,6 @@ QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip) } -/*! - Issues the GL draw call for \a geometryNode. - - The function assumes that attributes have been bound and set up prior - to making this call. - - \internal - */ - -void QSGRenderer::draw(const QSGBasicGeometryNode *node) -{ - const QSGGeometry *g = node->geometry(); - if (g->indexCount()) { - glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); - } else { - glDrawArrays(g->drawingMode(), 0, g->vertexCount()); - } -} - static inline int size_of_type(GLenum type) { @@ -528,19 +525,102 @@ static inline int size_of_type(GLenum type) 4, sizeof(double) }; + Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE return sizes[type - GL_BYTE]; } + +class QSGRendererVBOGeometryData : public QSGGeometryData +{ +public: + QSGRendererVBOGeometryData() + : vertexBuffer(0) + , indexBuffer(0) + { + } + + ~QSGRendererVBOGeometryData() + { + QGLFunctions *func = QGLContext::currentContext()->functions(); + if (vertexBuffer) + func->glDeleteBuffers(1, &vertexBuffer); + if (indexBuffer) + func->glDeleteBuffers(1, &indexBuffer); + } + + GLuint vertexBuffer; + GLuint indexBuffer; + + static QSGRendererVBOGeometryData *get(const QSGGeometry *g) { + QSGRendererVBOGeometryData *gd = static_cast(QSGGeometryData::data(g)); + if (!gd) { + gd = new QSGRendererVBOGeometryData; + QSGGeometryData::install(g, gd); + } + return gd; + } + +}; + +static inline GLenum qt_drawTypeForPattern(QSGGeometry::DataPattern p) +{ + Q_ASSERT(p > 0 && p <= 3); + static GLenum drawTypes[] = { 0, + GL_STREAM_DRAW, + GL_DYNAMIC_DRAW, + GL_STATIC_DRAW + }; + return drawTypes[p]; +} + + /*! - Convenience function to set up and bind the vertex data in \a g to the - required attribute positions defined in \a material. + Issues the GL draw call for the geometry \a g using the material \a shader. + + The function assumes that attributes have been bound and set up prior + to making this call. \internal */ -void QSGRenderer::bindGeometry(QSGMaterialShader *material, const QSGGeometry *g) +void QSGRenderer::draw(const QSGMaterialShader *shader, const QSGGeometry *g) { - char const *const *attrNames = material->attributeNames(); + // ### remove before final release... + static bool use_vbo = !QApplication::arguments().contains("--no-vbo"); + + const void *vertexData; + int vertexByteSize = g->vertexCount() * g->stride(); + if (use_vbo && g->vertexDataPattern() != QSGGeometry::AlwaysUploadPattern && vertexByteSize > 1024) { + + // The base pointer for a VBO is 0 + vertexData = 0; + + bool updateData = QSGGeometryData::hasDirtyVertexData(g); + QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g); + if (!gd->vertexBuffer) { + glGenBuffers(1, &gd->vertexBuffer); + updateData = true; + } + + glBindBuffer(GL_ARRAY_BUFFER, gd->vertexBuffer); + m_vertex_buffer_bound = true; + + if (updateData) { + glBufferData(GL_ARRAY_BUFFER, vertexByteSize, g->vertexData(), + qt_drawTypeForPattern(g->vertexDataPattern())); + QSGGeometryData::clearDirtyVertexData(g); + } + + } else { + if (m_vertex_buffer_bound) { + glBindBuffer(GL_ARRAY_BUFFER, 0); + m_vertex_buffer_bound = false; + } + vertexData = g->vertexData(); + } + + // Bind the vertices to attributes... + char const *const *attrNames = shader->attributeNames(); int offset = 0; for (int j = 0; attrNames[j]; ++j) { if (!*attrNames[j]) @@ -548,15 +628,62 @@ void QSGRenderer::bindGeometry(QSGMaterialShader *material, const QSGGeometry *g Q_ASSERT_X(j < g->attributeCount(), "QSGRenderer::bindGeometry()", "Geometry lacks attribute required by material"); const QSGGeometry::Attribute &a = g->attributes()[j]; Q_ASSERT_X(j == a.position, "QSGRenderer::bindGeometry()", "Geometry does not have continuous attribute positions"); + #if defined(QT_OPENGL_ES_2) GLboolean normalize = a.type != GL_FLOAT; #else GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE; #endif - glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->stride(), (char *) g->vertexData() + offset); + glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->stride(), (char *) vertexData + offset); offset += a.tupleSize * size_of_type(a.type); } + + // Set up the indices... + const void *indexData; + if (use_vbo && g->indexDataPattern() != QSGGeometry::AlwaysUploadPattern && g->indexCount() > 512) { + + // Base pointer for a VBO is 0 + indexData = 0; + + bool updateData = QSGGeometryData::hasDirtyIndexData(g); + QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g); + if (!gd->indexBuffer) { + glGenBuffers(1, &gd->indexBuffer); + updateData = true; + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->indexBuffer); + m_index_buffer_bound = true; + + if (updateData) { + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + g->indexCount() * (g->indexType() == GL_UNSIGNED_SHORT ? 2 : 4), + g->indexData(), + qt_drawTypeForPattern(g->indexDataPattern())); + QSGGeometryData::clearDirtyIndexData(g); + } + + } else { + if (m_index_buffer_bound) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + m_index_buffer_bound = false; + } + indexData = g->indexData(); + } + + + // draw the stuff... + if (g->indexCount()) { + glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), indexData); + } else { + glDrawArrays(g->drawingMode(), 0, g->vertexCount()); + } + + // We leave buffers bound for now... They will be reset by bind on next draw() or + // set back to 0 if next draw is not using VBOs + } + QT_END_NAMESPACE diff --git a/src/declarative/scenegraph/coreapi/qsgrenderer_p.h b/src/declarative/scenegraph/coreapi/qsgrenderer_p.h index 3fdcf06..842286b 100644 --- a/src/declarative/scenegraph/coreapi/qsgrenderer_p.h +++ b/src/declarative/scenegraph/coreapi/qsgrenderer_p.h @@ -137,8 +137,7 @@ signals: void sceneGraphChanged(); // Add, remove, ChangeFlags changes... protected: - void draw(const QSGBasicGeometryNode *geometry); - void bindGeometry(QSGMaterialShader *material, const QSGGeometry *g); + void draw(const QSGMaterialShader *material, const QSGGeometry *g); virtual void render() = 0; QSGRenderer::ClipType updateStencilClip(const QSGClipNode *clip); @@ -174,9 +173,12 @@ private: const QSGBindable *m_bindable; - bool m_changed_emitted : 1; - bool m_mirrored : 1; - bool m_is_rendering : 1; + uint m_changed_emitted : 1; + uint m_mirrored : 1; + uint m_is_rendering : 1; + + uint m_vertex_buffer_bound : 1; + uint m_index_buffer_bound : 1; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QSGRenderer::ClearMode) diff --git a/src/declarative/scenegraph/scenegraph.pri b/src/declarative/scenegraph/scenegraph.pri index aa9d2bc..6fea47b 100644 --- a/src/declarative/scenegraph/scenegraph.pri +++ b/src/declarative/scenegraph/scenegraph.pri @@ -11,7 +11,9 @@ HEADERS += \ $$PWD/coreapi/qsgmaterial.h \ $$PWD/coreapi/qsgnode.h \ $$PWD/coreapi/qsgnodeupdater_p.h \ - $$PWD/coreapi/qsgrenderer_p.h + $$PWD/coreapi/qsgrenderer_p.h \ + $$PWD/coreapi/qsggeometry_p.h + SOURCES += \ $$PWD/coreapi/qsgdefaultrenderer.cpp \ $$PWD/coreapi/qsggeometry.cpp \ -- 1.7.2.5