Implement VBO caching of larger geometries
authorGunnar Sletta <gunnar.sletta@nokia.com>
Tue, 9 Aug 2011 12:50:07 +0000 (14:50 +0200)
committerYoann Lopes <yoann.lopes@nokia.com>
Thu, 11 Aug 2011 07:28:45 +0000 (09:28 +0200)
Change-Id: If87b70b191a06448287b47d252a288b441af6302
Reviewed-on: http://codereview.qt.nokia.com/2784
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Yoann Lopes <yoann.lopes@nokia.com>
src/declarative/items/qsgtextnode.cpp
src/declarative/scenegraph/coreapi/qsgdefaultrenderer.cpp
src/declarative/scenegraph/coreapi/qsggeometry.cpp
src/declarative/scenegraph/coreapi/qsggeometry.h
src/declarative/scenegraph/coreapi/qsggeometry_p.h [new file with mode: 0644]
src/declarative/scenegraph/coreapi/qsgrenderer.cpp
src/declarative/scenegraph/coreapi/qsgrenderer_p.h
src/declarative/scenegraph/scenegraph.pri

index d36db1b..03a558f 100644 (file)
@@ -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;
 }
 
index 8af626e..ce05e76 100644 (file)
@@ -510,8 +510,7 @@ void QSGDefaultRenderer::renderNodes(const QDataBuffer<QSGGeometryNode *> &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++;
index 71b5cb6..6b622af 100644 (file)
@@ -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
index 4325030..f99eee3 100644 (file)
@@ -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 (file)
index 0000000..2fba155
--- /dev/null
@@ -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<QSGGeometry *>(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<QSGGeometry *>(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<QSGGeometry *>(g)->m_dirty_index_data = false; }
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGGEOMETRY_P_H
index 78551b1..5a137ad 100644 (file)
@@ -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<QSGRendererVBOGeometryData *>(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
index 3fdcf06..842286b 100644 (file)
@@ -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)
index aa9d2bc..6fea47b 100644 (file)
@@ -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 \