diff options
Diffstat (limited to 'src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp')
-rw-r--r-- | src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp | 2289 |
1 files changed, 2289 insertions, 0 deletions
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp new file mode 100644 index 0000000000..397d3b0a17 --- /dev/null +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -0,0 +1,2289 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgbatchrenderer_p.h" + +#include <QtCore/QElapsedTimer> + +#include <QtGui/QGuiApplication> +#include <QtGui/QOpenGLFramebufferObject> + +QT_BEGIN_NAMESPACE + +extern QByteArray qsgShaderRewriter_insertZAttributes(const char *input); + +namespace QSGBatchRenderer +{ + +const bool debug_render = qgetenv("QSG_RENDERER_DEBUG").contains("render"); +const bool debug_build = qgetenv("QSG_RENDERER_DEBUG").contains("build"); +const bool debug_change = qgetenv("QSG_RENDERER_DEBUG").contains("change"); +const bool debug_upload = qgetenv("QSG_RENDERER_DEBUG").contains("upload"); +const bool debug_roots = qgetenv("QSG_RENDERER_DEBUG").contains("roots"); +const bool debug_dump = qgetenv("QSG_RENDERER_DEBUG").contains("dump"); +const bool debug_noalpha = qgetenv("QSG_RENDERER_DEBUG").contains("noalpha"); +const bool debug_noopaque = qgetenv("QSG_RENDERER_DEBUG").contains("noopaque"); +const bool debug_noclip = qgetenv("QSG_RENDERER_DEBUG").contains("noclip"); + + +#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling()) +#define SHADOWNODE_TRAVERSE(NODE) for (QList<Node *>::const_iterator child = NODE->children.constBegin(); child != NODE->children.constEnd(); ++child) + +static inline int size_of_type(GLenum type) +{ + static int sizes[] = { + sizeof(char), + sizeof(unsigned char), + sizeof(short), + sizeof(unsigned short), + sizeof(int), + sizeof(unsigned int), + sizeof(float), + 2, + 3, + 4, + sizeof(double) + }; + Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE + return sizes[type - GL_BYTE]; +} + +bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; } +bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; } +bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; } +bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; } +bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; } + +QSGMaterial::Flag QSGMaterial_RequiresFullMatrixBit = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate); + +struct QMatrix4x4_Accessor +{ + float m[4][4]; + int flagBits; + + static bool isTranslate(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x1; } + static bool is2DSafe(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits < 0x8; } +}; + +const float OPAQUE_LIMIT = 0.999f; + +ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material) +{ + QSGMaterialType *type = material->type(); + Shader *shader = rewrittenShaders.value(type, 0); + if (shader) + return shader; + + QSGMaterialShader *s = material->createShader(); + + QOpenGLShaderProgram *p = s->program(); + p->addShaderFromSourceCode(QOpenGLShader::Vertex, + qsgShaderRewriter_insertZAttributes(s->vertexShader())); + p->addShaderFromSourceCode(QOpenGLShader::Fragment, + s->fragmentShader()); + + char const *const *attr = s->attributeNames(); + int i; + for (i = 0; attr[i]; ++i) { + if (*attr[i]) + p->bindAttributeLocation(attr[i], i); + } + p->bindAttributeLocation("_qt_order", i); + + p->link(); + if (!p->isLinked()) { + qDebug() << "Renderer failed shader compilation:" << endl << p->log(); + return 0; + } + + s->initialize(); + + shader = new Shader; + shader->program = s; + shader->pos_order = i; + shader->id_zRange = p->uniformLocation("_qt_zRange"); + shader->lastOpacity = 0; + + Q_ASSERT(shader->pos_order >= 0); + Q_ASSERT(shader->id_zRange >= 0); + + rewrittenShaders[type] = shader; + return shader; +} + +ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material) +{ + QSGMaterialType *type = material->type(); + Shader *shader = stockShaders.value(type, 0); + if (shader) + return shader; + + QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader()); + s->compile(); + s->initialize(); + + shader = new Shader(); + shader->program = s; + shader->id_zRange = -1; + shader->pos_order = -1; + shader->lastOpacity = 0; + + stockShaders[type] = shader; + + return shader; +} + +void ShaderManager::invalidated() +{ + qDeleteAll(stockShaders.values()); + stockShaders.clear(); + qDeleteAll(rewrittenShaders.values()); + rewrittenShaders.clear(); + delete blitProgram; + blitProgram = 0; +} + +void qsg_dumpShadowRoots(BatchRootInfo *i, int indent) +{ + static int extraIndent = 0; + ++extraIndent; + + QByteArray ind(indent + extraIndent + 10, ' '); + + if (!i) { + qDebug() << ind.constData() << "- no info"; + } else { + qDebug() << ind.constData() << "- parent:" << i->parentRoot; + for (QSet<Node *>::const_iterator it = i->subRoots.constBegin(); + it != i->subRoots.constEnd(); ++it) { + qDebug() << ind.constData() << "-" << *it; + qsg_dumpShadowRoots((*it)->rootInfo(), indent); + } + } + + --extraIndent; +} + +void qsg_dumpShadowRoots(Node *n) +{ + static int indent = 0; + ++indent; + + QByteArray ind(indent, ' '); + + if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) { + qDebug() << ind.constData() << "[X]" << n->sgNode << hex << uint(n->sgNode->flags()); + qsg_dumpShadowRoots(n->rootInfo(), indent); + } else { + qDebug() << ind.constData() << "[ ]" << n->sgNode << hex << uint(n->sgNode->flags()); + } + + SHADOWNODE_TRAVERSE(n) + qsg_dumpShadowRoots(*child); + + --indent; +} + +Updater::Updater(Renderer *r) + : renderer(r) + , m_roots(32) + , m_rootMatrices(8) +{ + m_roots.add(0); + m_combined_matrix_stack.add(&m_identityMatrix); + m_rootMatrices.add(m_identityMatrix); + + Q_ASSERT(sizeof(QMatrix4x4_Accessor) == sizeof(QMatrix4x4)); +} + +void Updater::updateStates(QSGNode *n) +{ + m_toplevel_alpha = 1; + m_current_clip = 0; + + m_added = 0; + m_transformChange = 0; + + Node *sn = renderer->m_nodes.value(n, 0); + Q_ASSERT(sn); + + if (Q_UNLIKELY(debug_roots)) + qsg_dumpShadowRoots(sn); + + if (Q_UNLIKELY(debug_build)) { + qDebug() << "Updater::updateStates()"; + if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16)) + qDebug() << " - nodes have been added"; + if (sn->dirtyState & (QSGNode::DirtyMatrix << 16)) + qDebug() << " - transforms have changed"; + if (sn->dirtyState & (QSGNode::DirtyOpacity << 16)) + qDebug() << " - opacity has changed"; + } + + visitNode(sn); +} + +void Updater::visitNode(Node *n) +{ + if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0) + return; + + int count = m_added; + if (n->dirtyState & QSGNode::DirtyNodeAdded) + ++m_added; + + switch (n->type()) { + case QSGNode::OpacityNodeType: + visitOpacityNode(n); + break; + case QSGNode::TransformNodeType: + visitTransformNode(n); + break; + case QSGNode::GeometryNodeType: + visitGeometryNode(n); + break; + case QSGNode::ClipNodeType: + visitClipNode(n); + break; + case QSGNode::RenderNodeType: + if (m_added) + n->renderNodeElement()->root = m_roots.last(); + // Fall through to visit children. + default: + SHADOWNODE_TRAVERSE(n) visitNode(*child); + break; + } + + m_added = count; + n->dirtyState = 0; +} + +void Updater::visitClipNode(Node *n) +{ + ClipBatchRootInfo *extra = n->clipInfo(); + + QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode); + + if (m_roots.last() && m_added > 0) + renderer->registerBatchRoot(n, m_roots.last()); + + cn->m_clip_list = m_current_clip; + m_current_clip = cn; + m_roots << n; + m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last()); + extra->matrix = m_rootMatrices.last(); + cn->m_matrix = &extra->matrix; + m_combined_matrix_stack << &m_identityMatrix; + + SHADOWNODE_TRAVERSE(n) visitNode(*child); + + m_current_clip = cn->m_clip_list; + m_rootMatrices.pop_back(); + m_combined_matrix_stack.pop_back(); + m_roots.pop_back(); +} + +void Updater::visitOpacityNode(Node *n) +{ + QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode); + + qreal combined = m_opacity_stack.last() * on->opacity(); + on->setCombinedOpacity(combined); + m_opacity_stack.add(combined); + + if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) { + bool was = n->isOpaque; + bool is = on->opacity() > OPAQUE_LIMIT; + if (was != is) { + renderer->m_rebuild = Renderer::FullRebuild; + n->isOpaque = is; + } + ++m_force_update; + SHADOWNODE_TRAVERSE(n) visitNode(*child); + --m_force_update; + } else { + if (m_added > 0) + n->isOpaque = on->opacity() > OPAQUE_LIMIT; + SHADOWNODE_TRAVERSE(n) visitNode(*child); + } + + m_opacity_stack.pop_back(); +} + +void Updater::visitTransformNode(Node *n) +{ + bool popMatrixStack = false; + bool popRootStack = false; + bool dirty = n->dirtyState & QSGNode::DirtyMatrix; + + QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode); + + if (n->isBatchRoot) { + if (m_added > 0 && m_roots.size() > 0) + renderer->registerBatchRoot(n, m_roots.last()); + tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix()); + + // The only change in this subtree is ourselves and we are a batch root, so + // only update subroots and return, saving tons of child-processing (flickable-panning) + + if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) { + BatchRootInfo *info = renderer->batchRootInfo(n); + for (QSet<Node *>::const_iterator it = info->subRoots.constBegin(); + it != info->subRoots.constEnd(); ++it) { + updateRootTransforms(*it, n, tn->combinedMatrix()); + } + return; + } + + n->becameBatchRoot = false; + + m_combined_matrix_stack.add(&m_identityMatrix); + m_roots.add(n); + m_rootMatrices.add(tn->combinedMatrix()); + + popMatrixStack = true; + popRootStack = true; + } else if (!tn->matrix().isIdentity()) { + tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix()); + m_combined_matrix_stack.add(&tn->combinedMatrix()); + popMatrixStack = true; + } else { + tn->setCombinedMatrix(*m_combined_matrix_stack.last()); + } + + if (dirty) + ++m_transformChange; + + SHADOWNODE_TRAVERSE(n) visitNode(*child); + + if (dirty) + --m_transformChange; + if (popMatrixStack) + m_combined_matrix_stack.pop_back(); + if (popRootStack) { + m_roots.pop_back(); + m_rootMatrices.pop_back(); + } +} + +void Updater::visitGeometryNode(Node *n) +{ + QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode); + + gn->m_matrix = m_combined_matrix_stack.last(); + gn->m_clip_list = m_current_clip; + gn->setInheritedOpacity(m_opacity_stack.last()); + + if (m_added) { + Element *e = n->element(); + e->root = m_roots.last(); + e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix()); + + if (e->root) { + BatchRootInfo *info = renderer->batchRootInfo(e->root); + info->availableOrders--; + if (info->availableOrders < 0) { + renderer->m_rebuild |= Renderer::BuildRenderLists; + } else { + renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots; + renderer->m_taggedRoots << e->root; + } + } else { + renderer->m_rebuild |= Renderer::FullRebuild; + } + } else if (m_transformChange) { + Element *e = n->element(); + e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix()); + } + + SHADOWNODE_TRAVERSE(n) visitNode(*child); +} + +void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined) +{ + BatchRootInfo *info = renderer->batchRootInfo(node); + QMatrix4x4 m; + Node *n = node; + + while (n != root) { + if (n->type() == QSGNode::TransformNodeType) + m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m; + n = n->parent; + } + + m = combined * m; + + if (node->type() == QSGNode::ClipNodeType) { + static_cast<ClipBatchRootInfo *>(info)->matrix = m; + } else { + Q_ASSERT(node->type() == QSGNode::TransformNodeType); + static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m); + } + + for (QSet<Node *>::const_iterator it = info->subRoots.constBegin(); + it != info->subRoots.constEnd(); ++it) { + updateRootTransforms(*it, node, m); + } +} + +int qsg_positionAttribute(QSGGeometry *g) { + int vaOffset = 0; + for (int a=0; a<g->attributeCount(); ++a) { + const QSGGeometry::Attribute &attr = g->attributes()[a]; + if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == GL_FLOAT) { + return vaOffset; + } + vaOffset += attr.tupleSize * size_of_type(attr.type); + } + return -1; +} + +void Element::computeBounds() +{ + Q_ASSERT(!boundsComputed); + boundsComputed = true; + + QSGGeometry *g = node->geometry(); + int offset = qsg_positionAttribute(g); + if (offset == -1) { + // No position attribute means overlaps with everything.. + bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX); + return; + } + + bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + char *vd = (char *) g->vertexData() + offset; + for (int i=0; i<g->vertexCount(); ++i) { + bounds |= *(Pt *) vd; + vd += g->sizeOfVertex(); + } + bounds.map(*node->matrix()); +} + +RenderNodeElement::~RenderNodeElement() +{ + delete fbo; +} + +bool Batch::isMaterialCompatible(Element *e) const +{ + // If material has changed between opaque and translucent, it is not compatible + QSGMaterial *m = e->node->activeMaterial(); + if (isOpaque != ((m->flags() & QSGMaterial::Blending) == 0)) + return false; + + Element *n = first; + // Skip to the first node other than e which has not been removed + while (n && (n == e || n->removed)) + n = n->nextInBatch; + + // Only 'e' in this batch, so a material change doesn't change anything as long as + // its blending is still in sync with this batch... + if (!n) + return true; + + QSGMaterial *nm = n->node->activeMaterial(); + return nm->type() == m->type() && nm->compare(m) == 0; +} + +/* + * Marks this batch as dirty or in the case where the geometry node has + * changed to be incompatible with this batch, return false so that + * the caller can mark the entire sg for a full rebuild... + */ +bool Batch::geometryWasChanged(QSGGeometryNode *gn) +{ + Element *e = first; + Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time"); + // 'gn' is the first node in the batch, compare against the next one. + while (e && (e->node == gn || e->removed)) + e = e->nextInBatch; + if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) { + needsUpload = true; + return true; + } else { + return false; + } +} + +void Batch::cleanupRemovedElements() +{ + // remove from front of batch.. + while (first && first->removed) { + first = first->nextInBatch; + } + + // Then continue and remove other nodes further out in the batch.. + if (first) { + Element *e = first; + while (e->nextInBatch) { + if (e->nextInBatch->removed) + e->nextInBatch = e->nextInBatch->nextInBatch; + else + e = e->nextInBatch; + + } + } +} + +/* + * Iterates through all geometry nodes in this batch and unsets their batch, + * thus forcing them to be rebuilt + */ +void Batch::invalidate() +{ + // If doing removal here is a performance issue, we might add a "hasRemoved" bit to + // the batch to do an early out.. + cleanupRemovedElements(); + Element *e = first; + first = 0; + root = 0; + while (e) { + e->batch = 0; + Element *n = e->nextInBatch; + e->nextInBatch = 0; + e = n; + } +} + +bool Batch::isTranslateOnlyToRoot() const { + bool only = true; + Element *e = first; + while (e && only) { + only &= e->translateOnlyToRoot; + e = e->nextInBatch; + } + return only; +} + +static int qsg_countNodesInBatch(const Batch *batch) +{ + int sum = 0; + Element *e = batch->first; + while (e) { + ++sum; + e = e->nextInBatch; + } + return sum; +} + +static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches) +{ + int sum = 0; + for (int i=0; i<batches.size(); ++i) { + sum += qsg_countNodesInBatch(batches.at(i)); + } + return sum; +} + +Renderer::Renderer(QSGContext *ctx) + : QSGRenderer(ctx) + , m_opaqueRenderList(64) + , m_alphaRenderList(64) + , m_nextRenderOrder(0) + , m_explicitOrdering(false) + , m_opaqueBatches(16) + , m_alphaBatches(16) + , m_batchPool(16) + , m_elementsToDelete(64) + , m_tmpAlphaElements(16) + , m_tmpOpaqueElements(16) + , m_rebuild(FullRebuild) + , m_zRange(0) + , m_currentMaterial(0) + , m_currentShader(0) +{ + setNodeUpdater(new Updater(this)); + + m_shaderManager = ctx->findChild<ShaderManager *>(QStringLiteral("__qt_ShaderManager"), Qt::FindDirectChildrenOnly); + if (!m_shaderManager) { + m_shaderManager = new ShaderManager(); + m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager")); + m_shaderManager->setParent(ctx); + QObject::connect(ctx, SIGNAL(invalidated()), m_shaderManager, SLOT(invalidated()), Qt::DirectConnection); + } + + m_bufferStrategy = GL_STATIC_DRAW; + QByteArray strategy = qgetenv("QSG_RENDERER_BUFFER_STRATEGY"); + if (strategy == "dynamic") { + m_bufferStrategy = GL_DYNAMIC_DRAW; + } else if (strategy == "stream") { + m_bufferStrategy = GL_STREAM_DRAW; + } + + m_batchNodeThreshold = 64; + QByteArray alternateThreshold = qgetenv("QSG_RENDERER_BATCH_NODE_THRESHOLD"); + if (alternateThreshold.length() > 0) { + bool ok = false; + int threshold = alternateThreshold.toInt(&ok); + if (ok) + m_batchNodeThreshold = threshold; + } + + m_batchVertexThreshold = 1024; + alternateThreshold = qgetenv("QSG_RENDERER_BATCH_VERTEX_THRESHOLD"); + if (alternateThreshold.length() > 0) { + bool ok = false; + int threshold = alternateThreshold.toInt(&ok); + if (ok) + m_batchVertexThreshold = threshold; + } + if (Q_UNLIKELY(debug_build || debug_render)) { + qDebug() << "Batch thresholds: nodes:" << m_batchNodeThreshold << " vertices:" << m_batchVertexThreshold; + qDebug() << "Using buffer strategy:" << (m_bufferStrategy == GL_STATIC_DRAW ? "static" : (m_bufferStrategy == GL_DYNAMIC_DRAW ? "dynamic" : "stream")); + } +} + +static void qsg_wipeBuffer(Buffer *buffer, QOpenGLFunctions *funcs) +{ + funcs->glDeleteBuffers(1, &buffer->id); + free(buffer->data); +} + +static void qsg_wipeBatch(Batch *batch, QOpenGLFunctions *funcs) +{ + qsg_wipeBuffer(&batch->vbo, funcs); + delete batch; +} + +Renderer::~Renderer() +{ + // Clean up batches and buffers + for (int i=0; i<m_opaqueBatches.size(); ++i) qsg_wipeBatch(m_opaqueBatches.at(i), this); + for (int i=0; i<m_alphaBatches.size(); ++i) qsg_wipeBatch(m_alphaBatches.at(i), this); + for (int i=0; i<m_batchPool.size(); ++i) qsg_wipeBatch(m_batchPool.at(i), this); + + // The shadowtree + qDeleteAll(m_nodes.values()); + + // Remaining elements... + for (int i=0; i<m_elementsToDelete.size(); ++i) { + Element *e = m_elementsToDelete.at(i); + if (e->isRenderNode) + delete static_cast<RenderNodeElement *>(e); + else + delete e; + } +} + +void Renderer::invalidateAndRecycleBatch(Batch *b) +{ + b->invalidate(); + for (int i=0; i<m_batchPool.size(); ++i) + if (b == m_batchPool.at(i)) + return; + m_batchPool.add(b); +} + +/* The code here does a CPU-side allocation which might seem like a performance issue + * compared to using glMapBuffer or glMapBufferRange which would give me back + * potentially GPU allocated memory and saving me one deep-copy, but... + * + * Because we do a lot of CPU-side transformations, we need random-access memory + * and the memory returned from glMapBuffer/glMapBufferRange is typically + * uncached and thus very slow for our purposes. + * + * ref: http://www.opengl.org/wiki/Buffer_Object + */ +void Renderer::map(Buffer *buffer, int byteSize) +{ + if (buffer->size != byteSize) { + if (buffer->data) + free(buffer->data); + buffer->data = (char *) malloc(byteSize); + buffer->size = byteSize; + } +} + +void Renderer::unmap(Buffer *buffer) +{ + if (buffer->id == 0) + glGenBuffers(1, &buffer->id); + glBindBuffer(GL_ARRAY_BUFFER, buffer->id); + glBufferData(GL_ARRAY_BUFFER, buffer->size, buffer->data, m_bufferStrategy); +} + +BatchRootInfo *Renderer::batchRootInfo(Node *node) +{ + BatchRootInfo *info = node->rootInfo(); + if (!info) { + if (node->type() == QSGNode::ClipNodeType) + info = new ClipBatchRootInfo; + else { + Q_ASSERT(node->type() == QSGNode::TransformNodeType); + info = new BatchRootInfo; + } + node->data = info; + } + return info; +} + +void Renderer::removeBatchRootFromParent(Node *childRoot) +{ + BatchRootInfo *childInfo = batchRootInfo(childRoot); + if (!childInfo->parentRoot) + return; + BatchRootInfo *parentInfo = batchRootInfo(childInfo->parentRoot); + + Q_ASSERT(parentInfo->subRoots.contains(childRoot)); + parentInfo->subRoots.remove(childRoot); + childInfo->parentRoot = 0; +} + +void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot) +{ + BatchRootInfo *subInfo = batchRootInfo(subRoot); + BatchRootInfo *parentInfo = batchRootInfo(parentRoot); + subInfo->parentRoot = parentRoot; + parentInfo->subRoots << subRoot; +} + +bool Renderer::changeBatchRoot(Node *node, Node *root) +{ + BatchRootInfo *subInfo = batchRootInfo(node); + if (subInfo->parentRoot == root) + return false; + if (subInfo->parentRoot) { + BatchRootInfo *oldRootInfo = batchRootInfo(subInfo->parentRoot); + oldRootInfo->subRoots.remove(node); + } + BatchRootInfo *newRootInfo = batchRootInfo(root); + newRootInfo->subRoots << node; + subInfo->parentRoot = root; + return true; +} + +void Renderer::nodeChangedBatchRoot(Node *node, Node *root) +{ + if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) { + if (!changeBatchRoot(node, root)) + return; + node = root; + } else if (node->type() == QSGNode::GeometryNodeType) { + // Only need to change the root as nodeChanged anyway flags a full update. + Element *e = node->element(); + if (e) + e->root = root; + } + + SHADOWNODE_TRAVERSE(node) + nodeChangedBatchRoot(*child, root); +} + +void Renderer::nodeWasTransformed(Node *node, int *vertexCount) +{ + if (node->type() == QSGNode::GeometryNodeType) { + QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode); + *vertexCount += gn->geometry()->vertexCount(); + Element *e = node->element(); + if (e) { + e->boundsComputed = false; + if (e->batch) { + if (!e->batch->isOpaque) { + e->batch->invalidate(); + m_rebuild |= BuildBatches; + } else if (e->batch->merged) { + e->batch->needsUpload = true; + } + } + if (e->batch && e->batch->merged) + e->batch->needsUpload = true; + } + } + + SHADOWNODE_TRAVERSE(node) + nodeWasTransformed(*child, vertexCount); +} + +void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent) +{ + Q_ASSERT(!m_nodes.contains(node)); + if (node->isSubtreeBlocked()) + return; + + Node *snode = new Node(node); + m_nodes.insert(node, snode); + if (shadowParent) { + snode->parent = shadowParent; + shadowParent->children.append(snode); + } + + if (node->type() == QSGNode::GeometryNodeType) { + snode->data = new Element(static_cast<QSGGeometryNode *>(node)); + + } else if (node->type() == QSGNode::ClipNodeType) { + snode->data = new ClipBatchRootInfo; + + } else if (node->type() == QSGNode::RenderNodeType) { + RenderNodeElement *e = new RenderNodeElement(static_cast<QSGRenderNode *>(node)); + snode->data = e; + Q_ASSERT(!m_renderNodeElements.contains(static_cast<QSGRenderNode *>(node))); + m_renderNodeElements.insert(e->renderNode, e); + } + + QSGNODE_TRAVERSE(node) + nodeWasAdded(child, snode); +} + +void Renderer::nodeWasRemoved(Node *node) +{ + // Prefix traversal as removeBatchFromParent below removes nodes + // in a bottom-up manner + SHADOWNODE_TRAVERSE(node) + nodeWasRemoved(*child); + + if (node->type() == QSGNode::GeometryNodeType) { + Element *e = node->element(); + if (e) { + e->removed = true; + m_elementsToDelete.add(e); + e->node = 0; + if (e->root) { + BatchRootInfo *info = batchRootInfo(e->root); + info->availableOrders++; + } + if (e->batch) { + e->batch->needsUpload = true; + } + + } + + } else if (node->type() == QSGNode::ClipNodeType) { + removeBatchRootFromParent(node); + delete node->clipInfo(); + m_rebuild |= FullRebuild; + m_taggedRoots.remove(node); + + } else if (node->isBatchRoot) { + removeBatchRootFromParent(node); + delete node->rootInfo(); + m_rebuild |= FullRebuild; + m_taggedRoots.remove(node); + + } else if (node->type() == QSGNode::RenderNodeType) { + RenderNodeElement *e = m_renderNodeElements.take(static_cast<QSGRenderNode *>(node->sgNode)); + if (e) { + e->removed = true; + m_elementsToDelete.add(e); + } + } + + Q_ASSERT(m_nodes.contains(node->sgNode)); + delete m_nodes.take(node->sgNode); +} + +void Renderer::turnNodeIntoBatchRoot(Node *node) +{ + if (Q_UNLIKELY(debug_change)) qDebug() << " - new batch root"; + m_rebuild |= FullRebuild; + node->isBatchRoot = true; + node->becameBatchRoot = true; + + Node *p = node->parent; + while (p) { + if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) { + registerBatchRoot(node, p); + break; + } + p = p->parent; + } + + SHADOWNODE_TRAVERSE(node) + nodeChangedBatchRoot(*child, node); +} + + +void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state) +{ + if (Q_UNLIKELY(debug_change)) { + QDebug debug = qDebug(); + debug << "dirty:"; + if (state & QSGNode::DirtyGeometry) + debug << "Geometry"; + if (state & QSGNode::DirtyMaterial) + debug << "Material"; + if (state & QSGNode::DirtyMatrix) + debug << "Matrix"; + if (state & QSGNode::DirtyNodeAdded) + debug << "Added"; + if (state & QSGNode::DirtyNodeRemoved) + debug << "Removed"; + if (state & QSGNode::DirtyOpacity) + debug << "Opacity"; + if (state & QSGNode::DirtySubtreeBlocked) + debug << "SubtreeBlocked"; + if (state & QSGNode::DirtyForceUpdate) + debug << "ForceUpdate"; + + // when removed, some parts of the node could already have been destroyed + // so don't debug it out. + if (state & QSGNode::DirtyNodeRemoved) + debug << (void *) node << node->type(); + else + debug << node; + } + + // As this function calls nodeChanged recursively, we do it at the top + // to avoid that any of the others are processed twice. + if (state & QSGNode::DirtySubtreeBlocked) { + Node *sn = m_nodes.value(node); + bool blocked = node->isSubtreeBlocked(); + if (blocked && sn) { + nodeChanged(node, QSGNode::DirtyNodeRemoved); + Q_ASSERT(m_nodes.value(node) == 0); + } else if (!blocked && !sn) { + nodeChanged(node, QSGNode::DirtyNodeAdded); + } + return; + } + + if (state & QSGNode::DirtyNodeAdded) { + if (nodeUpdater()->isNodeBlocked(node, rootNode())) { + QSGRenderer::nodeChanged(node, state); + return; + } + if (node == rootNode()) + nodeWasAdded(node, 0); + else + nodeWasAdded(node, m_nodes.value(node->parent())); + } + + // Mark this node dirty in the shadow tree. + Node *shadowNode = m_nodes.value(node); + + // Blocked subtrees won't have shadow nodes, so we can safely abort + // here.. + if (!shadowNode) { + QSGRenderer::nodeChanged(node, state); + return; + } + + shadowNode->dirtyState |= state; + + if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) { + Q_ASSERT(node->type() == QSGNode::TransformNodeType); + if (node->m_subtreeRenderableCount > m_batchNodeThreshold) { + turnNodeIntoBatchRoot(shadowNode); + } else { + int vertices = 0; + nodeWasTransformed(shadowNode, &vertices); + if (vertices > m_batchVertexThreshold) { + turnNodeIntoBatchRoot(shadowNode); + } + } + } + + if (state & QSGNode::DirtyGeometry && node->type() == QSGNode::GeometryNodeType) { + QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node); + Element *e = shadowNode->element(); + if (e) { + e->boundsComputed = false; + Batch *b = e->batch; + if (b) { + if (!e->batch->geometryWasChanged(gn)) { + m_rebuild |= Renderer::FullRebuild; + } else if (!b->isOpaque) { + m_rebuild |= Renderer::BuildBatches; + } else { + b->needsUpload = true; + } + } + } + } + + if (state & QSGNode::DirtyMaterial) { + Q_ASSERT(node->type() == QSGNode::GeometryNodeType); + Element *e = shadowNode->element(); + if (e) { + if (e->batch) { + if (!e->batch->isMaterialCompatible(e)) + m_rebuild = Renderer::FullRebuild; + } else { + m_rebuild = Renderer::BuildBatches; + } + } + } + + // Mark the shadow tree dirty all the way back to the root... + QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded + | QSGNode::DirtyOpacity + | QSGNode::DirtyMatrix + | QSGNode::DirtySubtreeBlocked); + if (dirtyChain != 0) { + dirtyChain = QSGNode::DirtyState(dirtyChain << 16); + Node *sn = shadowNode->parent; + while (sn) { + sn->dirtyState |= dirtyChain; + sn = sn->parent; + } + } + + // Delete happens at the very end because it deletes the shadownode. + if (state & QSGNode::DirtyNodeRemoved) { + Node *parent = shadowNode->parent; + if (parent) + parent->children.removeOne(shadowNode); + nodeWasRemoved(shadowNode); + Q_ASSERT(m_nodes.value(node) == 0); + } + + QSGRenderer::nodeChanged(node, state); +} + +/* + * Traverses the tree and builds two list of geometry nodes. One for + * the opaque and one for the translucent. These are populated + * in the order they should visually appear in, meaning first + * to the back and last to the front. + * + * We split opaque and translucent as we can perform different + * types of reordering / batching strategies on them, depending + * + * Note: It would be tempting to use the shadow nodes instead of the QSGNodes + * for traversal to avoid hash lookups, but the order of the children + * is important and they are not preserved in the shadow tree, so we must + * use the actual QSGNode tree. + */ +void Renderer::buildRenderLists(QSGNode *node) +{ + if (node->isSubtreeBlocked()) + return; + + Q_ASSERT(m_nodes.contains(node)); + Node *shadowNode = m_nodes.value(node); + + if (node->type() == QSGNode::GeometryNodeType) { + QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node); + + Element *e = shadowNode->element(); + Q_ASSERT(e); + + bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending); + if (opaque) + m_opaqueRenderList << e; + else + m_alphaRenderList << e; + + e->order = ++m_nextRenderOrder; + + // Used while rebuilding partial roots. + if (m_explicitOrdering) + e->orphaned = false; + + } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) { + Q_ASSERT(m_nodes.contains(node)); + BatchRootInfo *info = batchRootInfo(m_nodes.value(node)); + if (m_explicitOrdering) + m_nextRenderOrder = info->firstOrder; + int currentOrder = m_nextRenderOrder; + QSGNODE_TRAVERSE(node) + buildRenderLists(child); + int padding = (m_nextRenderOrder - currentOrder) >> 2; + if (m_explicitOrdering) { + m_nextRenderOrder = info->lastOrder + 1; + } else { + info->firstOrder = currentOrder; + info->availableOrders = padding; + info->lastOrder = m_nextRenderOrder + padding; + m_nextRenderOrder += padding; + } + return; + } else if (node->type() == QSGNode::RenderNodeType) { + RenderNodeElement *e = shadowNode->renderNodeElement(); + m_alphaRenderList << e; + e->order = ++m_nextRenderOrder; + Q_ASSERT(e); + } + + QSGNODE_TRAVERSE(node) + buildRenderLists(child); +} + +void Renderer::tagSubRoots(Node *node) +{ + BatchRootInfo *i = batchRootInfo(node); + m_taggedRoots << node; + for (QSet<Node *>::const_iterator it = i->subRoots.constBegin(); + it != i->subRoots.constEnd(); ++it) { + tagSubRoots(*it); + } +} + +static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList) +{ + orphans.reset(); + for (int i=0; i<renderList.size(); ++i) { + Element *e = renderList.at(i); + if (e && !e->removed && e->batch == 0) { + e->orphaned = true; + orphans.add(e); + } + } +} + +static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList) +{ + for (int i=0; i<orphans.size(); ++i) { + Element *e = orphans.at(i); + if (e->orphaned) + renderList.add(e); + } + orphans.reset(); +} + +/* + * To rebuild the tagged roots, we start by putting all subroots of tagged + * roots into the list of tagged roots. This is to make the rest of the + * algorithm simpler. + * + * Second, we invalidate all batches which belong to tagged roots, which now + * includes the entire subtree under a given root + * + * Then we call buildRenderLists for all tagged subroots which do not have + * parents which are tagged, aka, we traverse only the topmosts roots. + * + * Then we sort the render lists based on their render order, to restore the + * right order for rendering. + */ +void Renderer::buildRenderListsForTaggedRoots() +{ + // Flag any element that is currently in the render lists, but which + // is not in a batch. This happens when we have a partial rebuild + // in one sub tree while we have a BuildBatches change in another + // isolated subtree. So that batch-building takes into account + // these "orphaned" nodes, we flag them now. The ones under tagged + // roots will be cleared again. The remaining ones are added into the + // render lists so that they contain all visual nodes after the + // function completes. + qsg_addOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList); + qsg_addOrphanedElements(m_tmpAlphaElements, m_alphaRenderList); + + // Take a copy now, as we will be adding to this while traversing.. + QSet<Node *> roots = m_taggedRoots; + for (QSet<Node *>::const_iterator it = roots.constBegin(); + it != roots.constEnd(); ++it) { + tagSubRoots(*it); + } + + for (int i=0; i<m_opaqueBatches.size(); ++i) { + Batch *b = m_opaqueBatches.at(i); + if (m_taggedRoots.contains(b->root)) + invalidateAndRecycleBatch(b); + + } + for (int i=0; i<m_alphaBatches.size(); ++i) { + Batch *b = m_alphaBatches.at(i); + if (m_taggedRoots.contains(b->root)) + invalidateAndRecycleBatch(b); + } + + m_opaqueRenderList.reset(); + m_alphaRenderList.reset(); + int maxRenderOrder = m_nextRenderOrder; + m_explicitOrdering = true; + // Traverse each root, assigning it + for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin(); + it != m_taggedRoots.constEnd(); ++it) { + Node *root = *it; + BatchRootInfo *i = batchRootInfo(root); + if ((!i->parentRoot || !m_taggedRoots.contains(i->parentRoot)) + && !nodeUpdater()->isNodeBlocked(root->sgNode, rootNode())) { + m_nextRenderOrder = i->firstOrder; + buildRenderLists(root->sgNode); + } + } + m_taggedRoots.clear(); + m_explicitOrdering = false; + m_nextRenderOrder = qMax(m_nextRenderOrder, maxRenderOrder); + + // Add orphaned elements back into the list and then sort it.. + qsg_addBackOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList); + qsg_addBackOrphanedElements(m_tmpAlphaElements, m_alphaRenderList); + + if (m_opaqueRenderList.size()) + qSort(&m_opaqueRenderList.first(), &m_opaqueRenderList.last() + 1, qsg_sort_element_decreasing_order); + if (m_alphaRenderList.size()) + qSort(&m_alphaRenderList.first(), &m_alphaRenderList.last() + 1, qsg_sort_element_increasing_order); + +} + +void Renderer::buildRenderListsFromScratch() +{ + m_opaqueRenderList.reset(); + m_alphaRenderList.reset(); + + for (int i=0; i<m_opaqueBatches.size(); ++i) + invalidateAndRecycleBatch(m_opaqueBatches.at(i)); + for (int i=0; i<m_alphaBatches.size(); ++i) + invalidateAndRecycleBatch(m_alphaBatches.at(i)); + m_opaqueBatches.reset(); + m_alphaBatches.reset(); + + m_nextRenderOrder = 0; + + buildRenderLists(rootNode()); +} + +/* Clean up batches by making it a consecutive list of "valid" + * batches and moving all invalidated batches to the batches pool. + */ +void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) { + if (batches->size()) { + qSort(&batches->first(), &batches->last() + 1, qsg_sort_batch_is_valid); + int count = 0; + while (count < batches->size() && batches->at(count)->first) + ++count; + for (int i=count+1; i<batches->size(); ++i) + invalidateAndRecycleBatch(batches->at(i)); + batches->resize(count); + } +} + +void Renderer::prepareOpaqueBatches() +{ + for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) { + Element *ei = m_opaqueRenderList.at(i); + if (!ei || ei->batch) + continue; + Batch *batch = newBatch(); + batch->first = ei; + batch->root = ei->root; + batch->isOpaque = true; + batch->needsUpload = true; + batch->positionAttribute = qsg_positionAttribute(ei->node->geometry()); + + m_opaqueBatches.add(batch); + + ei->batch = batch; + Element *next = ei; + + QSGGeometryNode *gni = ei->node; + + for (int j = i - 1; j >= 0; --j) { + Element *ej = m_opaqueRenderList.at(j); + if (!ej) + continue; + if (ej->root != ei->root) + break; + if (ej->batch) + continue; + + QSGGeometryNode *gnj = ej->node; + + if (gni->clipList() == gnj->clipList() + && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() + && gni->geometry()->attributes() == gnj->geometry()->attributes() + && gni->inheritedOpacity() == gnj->inheritedOpacity() + && gni->activeMaterial()->type() == gnj->activeMaterial()->type() + && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { + ej->batch = batch; + next->nextInBatch = ej; + next = ej; + } + } + } +} + +bool Renderer::checkOverlap(int first, int last, const Rect &bounds) +{ + for (int i=first; i<=last; ++i) { + Element *e = m_alphaRenderList.at(i); + if (!e || e->batch) + continue; + Q_ASSERT(e->boundsComputed); + if (e->bounds.intersects(bounds)) + return true; + } + return false; +} + +/* + * + * To avoid the O(n^2) checkOverlap check in most cases, we have the + * overlapBounds which is the union of all bounding rects to check overlap + * for. We know that if it does not overlap, then none of the individual + * ones will either. For the typical list case, this results in no calls + * to checkOverlap what-so-ever. This also ensures that when all consecutive + * items are matching (such as a table of text), we don't build up an + * overlap bounds and thus do not require full overlap checks. + */ + +void Renderer::prepareAlphaBatches() +{ + for (int i=0; i<m_alphaRenderList.size(); ++i) { + Element *e = m_alphaRenderList.at(i); + if (!e || e->isRenderNode) + continue; + Q_ASSERT(!e->removed); + e->ensureBoundsValid(); + } + + for (int i=0; i<m_alphaRenderList.size(); ++i) { + Element *ei = m_alphaRenderList.at(i); + if (!ei || ei->batch) + continue; + + if (ei->isRenderNode) { + Batch *rnb = newBatch(); + rnb->first = ei; + rnb->root = ei->root; + rnb->isOpaque = false; + rnb->isRenderNode = true; + ei->batch = rnb; + m_alphaBatches.add(rnb); + continue; + } + + Batch *batch = newBatch(); + batch->first = ei; + batch->root = ei->root; + batch->isOpaque = false; + batch->needsUpload = true; + m_alphaBatches.add(batch); + ei->batch = batch; + + QSGGeometryNode *gni = ei->node; + batch->positionAttribute = qsg_positionAttribute(gni->geometry()); + + Rect overlapBounds; + overlapBounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + + Element *next = ei; + + for (int j = i + 1; j < m_alphaRenderList.size(); ++j) { + Element *ej = m_alphaRenderList.at(j); + if (!ej) + continue; + if (ej->root != ei->root || ej->isRenderNode) + break; + if (ej->batch) + continue; + + QSGGeometryNode *gnj = ej->node; + + if (gni->clipList() == gnj->clipList() + && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() + && gni->geometry()->attributes() == gnj->geometry()->attributes() + && gni->inheritedOpacity() == gnj->inheritedOpacity() + && gni->activeMaterial()->type() == gnj->activeMaterial()->type() + && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { + if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) { + ej->batch = batch; + next->nextInBatch = ej; + next = ej; + } + } else { + overlapBounds |= ej->bounds; + } + } + } + + +} + +/* These parameters warrant some explanation... + * + * vaOffset: The byte offset into the vertex data to the location of the + * 2D float point vertex attributes. + * + * vertexData: destination where the geometry's vertex data should go + * + * zData: destination of geometries injected Z positioning + * + * indexData: destination of the indices for this element + * + * iBase: The starting index for this element in the batch + */ + +void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, quint16 *iBase, int *indexCount) +{ + if (Q_UNLIKELY(debug_upload)) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData); + QSGGeometry *g = e->node->geometry(); + + const QMatrix4x4 &localx = *e->node->matrix(); + + const int vCount = g->vertexCount(); + const int vSize = g->sizeOfVertex(); + memcpy(*vertexData, g->vertexData(), vSize * vCount); + + // apply vertex transform.. + char *vdata = *vertexData + vaOffset; + if (((const QMatrix4x4_Accessor &) localx).flagBits == 1) { + for (int i=0; i<vCount; ++i) { + Pt *p = (Pt *) vdata; + p->x += ((QMatrix4x4_Accessor &) localx).m[3][0]; + p->y += ((QMatrix4x4_Accessor &) localx).m[3][1]; + vdata += vSize; + } + } else if (((const QMatrix4x4_Accessor &) localx).flagBits > 1) { + for (int i=0; i<vCount; ++i) { + ((Pt *) vdata)->map(localx); + vdata += vSize; + } + } + + float *vzorder = (float *) *zData; + float zorder = 1.0f - e->order * m_zRange; + for (int i=0; i<vCount; ++i) + vzorder[i] = zorder; + + int iCount = g->indexCount(); + quint16 *indices = (quint16 *) *indexData; + + if (iCount == 0) { + if (g->drawingMode() == GL_TRIANGLE_STRIP) + *indices++ = *iBase; + iCount = vCount; + for (int i=0; i<iCount; ++i) + indices[i] = *iBase + i; + } else { + const quint16 *srcIndices = g->indexDataAsUShort(); + if (g->drawingMode() == GL_TRIANGLE_STRIP) + *indices++ = *iBase + srcIndices[0]; + for (int i=0; i<iCount; ++i) + indices[i] = *iBase + srcIndices[i]; + } + if (g->drawingMode() == GL_TRIANGLE_STRIP) { + indices[iCount] = indices[iCount - 1]; + iCount += 2; + } + + *vertexData += vCount * vSize; + *zData += vCount * sizeof(float); + *indexData += iCount * sizeof(quint16); + *iBase += vCount; + *indexCount += iCount; +} + +const QMatrix4x4 &Renderer::matrixForRoot(Node *node) +{ + if (node->type() == QSGNode::TransformNodeType) + return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix(); + Q_ASSERT(node->type() == QSGNode::ClipNodeType); + QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode); + return *c->matrix(); +} + +void Renderer::uploadBatch(Batch *b) +{ + // Early out if nothing has changed in this batch.. + if (!b->needsUpload) { + if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch:" << b << "already uploaded..."; + return; + } + + if (!b->first) { + if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch:" << b << "is invalid..."; + return; + } + + if (b->isRenderNode) { + if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch: " << b << "is a render node..."; + return; + } + + // Figure out if we can merge or not, if not, then just render the batch as is.. + Q_ASSERT(b->first); + Q_ASSERT(b->first->node); + + QSGGeometryNode *gn = b->first->node; + QSGGeometry *g = gn->geometry(); + + bool canMerge = (g->drawingMode() == GL_TRIANGLES || g->drawingMode() == GL_TRIANGLE_STRIP) + && b->positionAttribute >= 0 + && g->indexType() == GL_UNSIGNED_SHORT + && QMatrix4x4_Accessor::is2DSafe(*gn->matrix()) + && (gn->activeMaterial()->flags() & QSGMaterial::CustomCompileStep) == 0 + && (((gn->activeMaterial()->flags() & QSGMaterial::RequiresDeterminant) == 0) + || (((gn->activeMaterial()->flags() & QSGMaterial_RequiresFullMatrixBit) == 0) && b->isTranslateOnlyToRoot()) + ); + + b->merged = canMerge; + + // Figure out how much memory we need... + b->vertexCount = 0; + b->indexCount = 0; + int unmergedIndexSize = 0; + Element *e = b->first; + + while (e) { + QSGGeometry *eg = e->node->geometry(); + b->vertexCount += eg->vertexCount(); + int iCount = eg->indexCount(); + if (b->merged) { + if (iCount == 0) + iCount = eg->vertexCount(); + // merged Triangle strips need to contain degenerate triangles at the beginning and end. + // One could save 2 ushorts here by ditching the the padding for the front of the + // first and the end of the last, but for simplicity, we simply don't care. + if (g->drawingMode() == GL_TRIANGLE_STRIP) + iCount += sizeof(quint16); + } else { + unmergedIndexSize += iCount * eg->sizeOfIndex(); + } + b->indexCount += iCount; + e = e->nextInBatch; + } + + // Abort if there are no vertices in this batch.. We abort this late as + // this is a broken usecase which we do not care to optimize for... + if (b->vertexCount == 0 || (b->merged && b->indexCount == 0)) + return; + + /* Allocate memory for this batch. Merged batches are divided into three separate blocks + 1. Vertex data for all elements, as they were in the QSGGeometry object, but + with the tranform relative to this batch's root applied. The vertex data + is otherwise unmodified. + 2. Z data for all elements, derived from each elements "render order". + This is present for merged data only. + 3. Indices for all elements, as they were in the QSGGeometry object, but + adjusted so that each index matches its. + And for TRIANGLE_STRIPs, we need to insert degenerate between each + primitive. These are unsigned shorts for merged and arbitrary for + non-merged. + */ + int bufferSize = b->vertexCount * g->sizeOfVertex(); + if (b->merged) + bufferSize += b->vertexCount * sizeof(float) + b->indexCount * sizeof(quint16); + else + bufferSize += unmergedIndexSize; + map(&b->vbo, bufferSize); + + if (Q_UNLIKELY(debug_upload)) qDebug() << " - batch" << b << " first:" << b->first << " root:" + << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute + << " vbo:" << b->vbo.id << ":" << b->vbo.size; + + if (b->merged) { + char *vertexData = b->vbo.data; + char *zData = vertexData + b->vertexCount * g->sizeOfVertex(); + char *indexData = zData + b->vertexCount * sizeof(float); + + quint16 iOffset = 0; + e = b->first; + int verticesInSet = 0; + int indicesInSet = 0; + b->drawSets.reset(); + b->drawSets << DrawSet(0, zData - vertexData, indexData - vertexData); + while (e) { + verticesInSet += e->node->geometry()->vertexCount(); + if (verticesInSet > 0xffff) { + b->drawSets.last().indexCount = indicesInSet; + b->drawSets << DrawSet(vertexData - b->vbo.data, + zData - b->vbo.data, + indexData - b->vbo.data); + iOffset = 0; + verticesInSet = e->node->geometry()->vertexCount(); + indicesInSet = 0; + } + uploadMergedElement(e, b->positionAttribute, &vertexData, &zData, &indexData, &iOffset, &indicesInSet); + e = e->nextInBatch; + } + b->drawSets.last().indexCount = indicesInSet; + } else { + char *vboData = b->vbo.data; + char *iboData = vboData + b->vertexCount * g->sizeOfVertex(); + Element *e = b->first; + while (e) { + QSGGeometry *g = e->node->geometry(); + int vbs = g->vertexCount() * g->sizeOfVertex(); + memcpy(vboData, g->vertexData(), vbs); + vboData = vboData + vbs; + if (g->indexCount()) { + int ibs = g->indexCount() * g->sizeOfIndex(); + memcpy(iboData, g->indexData(), ibs); + iboData += ibs; + } + e = e->nextInBatch; + } + } + + if (Q_UNLIKELY(debug_upload)) { + const char *vd = b->vbo.data; + qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex"; + for (int i=0; i<b->vertexCount; ++i) { + QDebug dump = qDebug().nospace(); + dump << " --- " << i << ": "; + int offset = 0; + for (int a=0; a<g->attributeCount(); ++a) { + const QSGGeometry::Attribute &attr = g->attributes()[a]; + dump << attr.position << ":(" << attr.tupleSize << ","; + if (attr.type == GL_FLOAT) { + dump << "float "; + if (attr.isVertexCoordinate) + dump << "* "; + for (int t=0; t<attr.tupleSize; ++t) + dump << *(float *)(vd + offset + t * sizeof(float)) << " "; + } else if (attr.type == GL_UNSIGNED_BYTE) { + dump << "ubyte "; + for (int t=0; t<attr.tupleSize; ++t) + dump << *(unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " "; + } + dump << ") "; + offset += attr.tupleSize * size_of_type(attr.type); + } + if (b->merged) { + float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i]; + dump << " Z:(" << zorder << ")"; + } + vd += g->sizeOfVertex(); + } + + const quint16 *id = (const quint16 *) (b->vbo.data + + b->vertexCount * g->sizeOfVertex() + + (b->merged ? b->vertexCount * sizeof(float) : 0)); + { + QDebug iDump = qDebug(); + iDump << " -- Index Data, count:" << b->indexCount; + for (int i=0; i<b->indexCount; ++i) { + if ((i % 24) == 0) + iDump << endl << " --- "; + iDump << id[i]; + } + } + + for (int i=0; i<b->drawSets.size(); ++i) { + const DrawSet &s = b->drawSets.at(i); + qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices; + } + } + + unmap(&b->vbo); + + if (Q_UNLIKELY(debug_upload)) qDebug() << " --- vertex/index buffers unmapped, batch upload completed..."; + + b->needsUpload = false; + + if (Q_UNLIKELY(debug_render)) + b->uploadedThisFrame = true; +} + +void Renderer::updateClip(const QSGClipNode *clipList, const Batch *batch) +{ + if (clipList != m_currentClip && Q_LIKELY(!debug_noclip)) { + m_currentClip = clipList; + // updateClip sets another program, so force-reactivate our own + if (m_currentShader) + setActiveShader(0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + if (batch->isOpaque) + glDisable(GL_DEPTH_TEST); + ClipType type = updateStencilClip(m_currentClip); + if (batch->isOpaque) { + glEnable(GL_DEPTH_TEST); + if (type & StencilClip) + glDepthMask(true); + } + } +} + +/*! + * Look at the attribute arrays and potentially the injected z attribute to figure out + * which vertex attribute arrays need to be enabled and not. Then update the current + * Shader and current QSGMaterialShader. + */ +void Renderer::setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader) +{ + const char * const *c = m_currentProgram ? m_currentProgram->attributeNames() : 0; + const char * const *n = program ? program->attributeNames() : 0; + + int cza = m_currentShader ? m_currentShader->pos_order : -1; + int nza = shader ? shader->pos_order : -1; + + int i = 0; + while (c || n) { + + bool was = c; + if (cza == i) { + was = true; + c = 0; + } else if (c && !c[i]) { // end of the attribute array names + c = 0; + was = false; + } + + bool is = n; + if (nza == i) { + is = true; + n = 0; + } else if (n && !n[i]) { + n = 0; + is = false; + } + + if (is && !was) + glEnableVertexAttribArray(i); + else if (was && !is) + glDisableVertexAttribArray(i); + + ++i; + } + + if (m_currentProgram) + m_currentProgram->deactivate(); + m_currentProgram = program; + m_currentShader = shader; + m_currentMaterial = 0; + if (m_currentProgram) { + m_currentProgram->program()->bind(); + m_currentProgram->activate(); + } +} + +void Renderer::renderMergedBatch(const Batch *batch) +{ + if (batch->vertexCount == 0 || batch->indexCount == 0) + return; + + Element *e = batch->first; + Q_ASSERT(e); + + if (Q_UNLIKELY(debug_render)) { + QDebug debug = qDebug(); + debug << " -" + << (batch->uploadedThisFrame ? "[ upload]" : "[retained]") + << (e->node->clipList() ? "[ clip]" : "[noclip]") + << (batch->isOpaque ? "[opaque]" : "[ alpha]") + << "[ merged]" + << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData() + << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData() + << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData() + << " root:" << batch->root; + if (batch->drawSets.size() > 1) + debug << "sets:" << batch->drawSets.size(); + batch->uploadedThisFrame = false; + } + + QSGGeometryNode *gn = e->node; + + // We always have dirty matrix as all batches are at a unique z range. + QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix; + if (batch->root) + m_current_model_view_matrix = matrixForRoot(batch->root); + else + m_current_model_view_matrix.setToIdentity(); + m_current_determinant = m_current_model_view_matrix.determinant(); + m_current_projection_matrix = projectionMatrix(); // has potentially been changed by renderUnmergedBatch.. + + // updateClip() uses m_current_projection_matrix. + updateClip(gn->clipList(), batch); + + glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vbo.id); + + + QSGMaterial *material = gn->activeMaterial(); + ShaderManager::Shader *sms = m_shaderManager->prepareMaterial(material); + QSGMaterialShader *program = sms->program; + + if (m_currentShader != sms) + setActiveShader(program, sms); + + m_current_opacity = gn->inheritedOpacity(); + if (sms->lastOpacity != m_current_opacity) { + dirty |= QSGMaterialShader::RenderState::DirtyOpacity; + sms->lastOpacity = m_current_opacity; + } + + program->updateState(state(dirty), material, m_currentMaterial); + + m_currentMaterial = material; + + QSGGeometry* g = gn->geometry(); + char const *const *attrNames = program->attributeNames(); + for (int i=0; i<batch->drawSets.size(); ++i) { + const DrawSet &draw = batch->drawSets.at(i); + int offset = 0; + for (int j = 0; attrNames[j]; ++j) { + if (!*attrNames[j]) + continue; + const QSGGeometry::Attribute &a = g->attributes()[j]; + GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE; + glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + draw.vertices)); + offset += a.tupleSize * size_of_type(a.type); + } + glVertexAttribPointer(sms->pos_order, 1, GL_FLOAT, false, 0, (void *) (qintptr) (draw.zorders)); + + glDrawElements(g->drawingMode(), draw.indexCount, GL_UNSIGNED_SHORT, (void *) (qintptr) (draw.indices)); + } +} + +void Renderer::renderUnmergedBatch(const Batch *batch) +{ + if (batch->vertexCount == 0) + return; + + Element *e = batch->first; + Q_ASSERT(e); + + if (Q_UNLIKELY(debug_render)) { + qDebug() << " -" + << (batch->uploadedThisFrame ? "[ upload]" : "[retained]") + << (e->node->clipList() ? "[ clip]" : "[noclip]") + << (batch->isOpaque ? "[opaque]" : "[ alpha]") + << "[unmerged]" + << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData() + << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData() + << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData() + << " root:" << batch->root; + + batch->uploadedThisFrame = false; + } + + QSGGeometryNode *gn = e->node; + + m_current_projection_matrix = projectionMatrix(); + updateClip(gn->clipList(), batch); + + glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id); + if (batch->indexCount) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vbo.id); + + // We always have dirty matrix as all batches are at a unique z range. + QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix; + + QSGMaterial *material = gn->activeMaterial(); + ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material); + QSGMaterialShader *program = sms->program; + + if (sms != m_currentShader) + setActiveShader(program, sms); + + m_current_opacity = gn->inheritedOpacity(); + if (sms->lastOpacity != m_current_opacity) { + dirty |= QSGMaterialShader::RenderState::DirtyOpacity; + sms->lastOpacity = m_current_opacity; + } + + int vOffset = 0; + int iOffset = batch->vertexCount * gn->geometry()->sizeOfVertex(); + + QMatrix4x4 rootMatrix = batch->root ? matrixForRoot(batch->root) : QMatrix4x4(); + + while (e) { + gn = e->node; + + m_current_model_view_matrix = rootMatrix * *gn->matrix(); + m_current_determinant = m_current_model_view_matrix.determinant(); + + m_current_projection_matrix = projectionMatrix(); + m_current_projection_matrix(2, 2) = m_zRange; + m_current_projection_matrix(2, 3) = 1.0f - e->order * m_zRange; + + program->updateState(state(dirty), material, m_currentMaterial); + + m_currentMaterial = gn->activeMaterial(); + + QSGGeometry* g = gn->geometry(); + char const *const *attrNames = program->attributeNames(); + int offset = 0; + for (int j = 0; attrNames[j]; ++j) { + if (!*attrNames[j]) + continue; + const QSGGeometry::Attribute &a = g->attributes()[j]; + GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE; + glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + vOffset)); + offset += a.tupleSize * size_of_type(a.type); + } + + if (g->indexCount()) + glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), (void *) (qintptr) iOffset); + else + glDrawArrays(g->drawingMode(), 0, g->vertexCount()); + + vOffset += g->sizeOfVertex() * g->vertexCount(); + iOffset += g->indexCount() * g->sizeOfIndex(); + + // We only need to push this on the very first iteration... + dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity; + + e = e->nextInBatch; + } +} + +void Renderer::renderBatches() +{ + if (Q_UNLIKELY(debug_render)) { + qDebug().nospace() << "Rendering:" << endl + << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << endl + << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches..."; + } + + QRect r = viewportRect(); + glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height()); + + for (QHash<QSGRenderNode *, RenderNodeElement *>::const_iterator it = m_renderNodeElements.constBegin(); + it != m_renderNodeElements.constEnd(); ++it) { + prepareRenderNode(it.value()); + } + + glClearColor(clearColor().redF(), clearColor().greenF(), clearColor().blueF(), clearColor().alphaF()); +#if defined(QT_OPENGL_ES) + glClearDepthf(1); +#else + glClearDepth(1); +#endif + + glDisable(GL_CULL_FACE); + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(true); + glColorMask(true, true, true, true); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + + bindable()->clear(clearMode()); + + m_current_opacity = 1; + m_currentMaterial = 0; + m_currentShader = 0; + m_currentProgram = 0; + m_currentClip = 0; + + bool renderOpaque = !debug_noopaque; + bool renderAlpha = !debug_noalpha; + + if (Q_LIKELY(renderOpaque)) { + for (int i=0; i<m_opaqueBatches.size(); ++i) { + Batch *b = m_opaqueBatches.at(i); + if (b->merged) + renderMergedBatch(b); + else + renderUnmergedBatch(b); + } + } + + glEnable(GL_BLEND); + glDepthMask(false); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + if (Q_LIKELY(renderAlpha)) { + for (int i=0; i<m_alphaBatches.size(); ++i) { + Batch *b = m_alphaBatches.at(i); + if (b->merged) + renderMergedBatch(b); + else if (b->isRenderNode) + renderRenderNode(b); + else + renderUnmergedBatch(b); + } + } + + if (m_currentShader) + setActiveShader(0, 0); + updateStencilClip(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + +} + +void Renderer::deleteRemovedElements() +{ + if (!m_elementsToDelete.size()) + return; + + for (int i=0; i<m_opaqueRenderList.size(); ++i) { + Element **e = m_opaqueRenderList.data() + i; + if (*e && (*e)->removed) + *e = 0; + } + for (int i=0; i<m_alphaRenderList.size(); ++i) { + Element **e = m_alphaRenderList.data() + i; + if (*e && (*e)->removed) + *e = 0; + } + + for (int i=0; i<m_elementsToDelete.size(); ++i) { + Element *e = m_elementsToDelete.at(i); + if (e->isRenderNode) + delete static_cast<RenderNodeElement *>(e); + else + delete e; + } + m_elementsToDelete.reset(); +} + +void Renderer::render() +{ + if (Q_UNLIKELY(debug_dump)) { + printf("\n\n"); + QSGNodeDumper::dump(rootNode()); + } + + if (Q_UNLIKELY(debug_render || debug_build)) { + + QByteArray type("rebuild:"); + if (m_rebuild == 0) + type += " none"; + if (m_rebuild == FullRebuild) + type += " full"; + else { + if (m_rebuild & BuildRenderLists) + type += " renderlists"; + else if (m_rebuild & BuildRenderListsForTaggedRoots) + type += " partial"; + else if (m_rebuild & BuildBatches) + type += " batches"; + } + + + qDebug() << "Renderer::render()" << this << type; + } + + if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) { + bool complete = (m_rebuild & BuildRenderLists) != 0; + if (complete) + buildRenderListsFromScratch(); + else + buildRenderListsForTaggedRoots(); + m_rebuild |= BuildBatches; + + if (Q_UNLIKELY(debug_build)) { + qDebug() << "Opaque render lists" << (complete ? "(complete)" : "(partial)") << ":"; + for (int i=0; i<m_opaqueRenderList.size(); ++i) { + Element *e = m_opaqueRenderList.at(i); + qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order; + } + qDebug() << "Alpha render list:" << (complete ? "(complete)" : "(partial)") << ":"; + for (int i=0; i<m_alphaRenderList.size(); ++i) { + Element *e = m_alphaRenderList.at(i); + qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order; + } + } + } + + for (int i=0; i<m_opaqueBatches.size(); ++i) + m_opaqueBatches.at(i)->cleanupRemovedElements(); + for (int i=0; i<m_alphaBatches.size(); ++i) + m_alphaBatches.at(i)->cleanupRemovedElements(); + deleteRemovedElements(); + + cleanupBatches(&m_opaqueBatches); + cleanupBatches(&m_alphaBatches); + + + if (m_rebuild & BuildBatches) { + prepareOpaqueBatches(); + prepareAlphaBatches(); + + if (Q_UNLIKELY(debug_build)) { + qDebug() << "Opaque Batches:"; + for (int i=0; i<m_opaqueBatches.size(); ++i) { + Batch *b = m_opaqueBatches.at(i); + qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root; + for (Element *e = b->first; e; e = e->nextInBatch) { + qDebug() << " - element:" << e << " node:" << e->node << e->order; + } + } + qDebug() << "Alpha Batches:"; + for (int i=0; i<m_alphaBatches.size(); ++i) { + Batch *b = m_alphaBatches.at(i); + qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root; + for (Element *e = b->first; e; e = e->nextInBatch) { + qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order; + } + } + } + } + + deleteRemovedElements(); + + // Then sort opaque batches so that we're drawing the batches with the highest + // order first, maximizing the benefit of front-to-back z-ordering. + if (m_opaqueBatches.size()) + qSort(&m_opaqueBatches.first(), &m_opaqueBatches.last() + 1, qsg_sort_batch_decreasing_order); + + // Sort alpha batches back to front so that they render correctly. + if (m_alphaBatches.size()) + qSort(&m_alphaBatches.first(), &m_alphaBatches.last() + 1, qsg_sort_batch_increasing_order); + + m_zRange = 1.0 / (m_nextRenderOrder); + + if (Q_UNLIKELY(debug_upload)) qDebug() << "Uploading Opaque Batches:"; + for (int i=0; i<m_opaqueBatches.size(); ++i) + uploadBatch(m_opaqueBatches.at(i)); + + if (Q_UNLIKELY(debug_upload)) qDebug() << "Uploading Alpha Batches:"; + for (int i=0; i<m_alphaBatches.size(); ++i) + uploadBatch(m_alphaBatches.at(i)); + + renderBatches(); + + m_rebuild = 0; +} + +void Renderer::prepareRenderNode(RenderNodeElement *e) +{ + if (e->fbo && e->fbo->size() != deviceRect().size()) { + delete e->fbo; + e->fbo = 0; + } + + if (!e->fbo) + e->fbo = new QOpenGLFramebufferObject(deviceRect().size(), QOpenGLFramebufferObject::CombinedDepthStencil); + e->fbo->bind(); + + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + QSGRenderNode::RenderState state; + QMatrix4x4 pm = projectionMatrix(); + state.projectionMatrix = ± + state.scissorEnabled = false; + state.stencilEnabled = false; + + QSGNode *clip = e->renderNode->parent(); + e->renderNode->m_clip_list = 0; + while (clip != rootNode()) { + if (clip->type() == QSGNode::ClipNodeType) { + e->renderNode->m_clip_list = static_cast<QSGClipNode *>(clip); + break; + } + clip = clip->parent(); + } + + QSGNode *xform = e->renderNode->parent(); + QMatrix4x4 matrix; + while (xform != rootNode()) { + if (xform->type() == QSGNode::TransformNodeType) { + matrix = matrixForRoot(e->root) * static_cast<QSGTransformNode *>(xform)->combinedMatrix(); + break; + } + xform = xform->parent(); + } + e->renderNode->m_matrix = &matrix; + + QSGNode *opacity = e->renderNode->parent(); + e->renderNode->m_opacity = 1.0; + while (opacity != rootNode()) { + if (opacity->type() == QSGNode::OpacityNodeType) { + e->renderNode->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity(); + break; + } + opacity = opacity->parent(); + } + + e->renderNode->render(state); + + e->renderNode->m_matrix = 0; + + bindable()->bind(); +} + +void Renderer::renderRenderNode(Batch *batch) +{ + updateStencilClip(0); + m_currentClip = 0; + + setActiveShader(0, 0); + + if (!m_shaderManager->blitProgram) { + m_shaderManager->blitProgram = new QOpenGLShaderProgram(); + const char *vs = + "attribute highp vec4 av; " + "attribute highp vec2 at; " + "varying highp vec2 t; " + "void main() { " + " gl_Position = av; " + " t = at; " + "} "; + const char *fs = + "uniform lowp sampler2D tex; " + "varying highp vec2 t; " + "void main() { " + " gl_FragColor = texture2D(tex, t); " + "} "; + + m_shaderManager->blitProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vs); + m_shaderManager->blitProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fs); + m_shaderManager->blitProgram->bindAttributeLocation("av", 0); + m_shaderManager->blitProgram->bindAttributeLocation("at", 1); + m_shaderManager->blitProgram->link(); + + Q_ASSERT(m_shaderManager->blitProgram->isLinked()); + } + + RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first); + glBindTexture(GL_TEXTURE_2D, e->fbo->texture()); + + m_shaderManager->blitProgram->bind(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + float z = 1.0f - e->order * m_zRange; + + float av[] = { -1, -1, z, + 1, -1, z, + -1, 1, z, + 1, 1, z }; + float at[] = { 0, 0, + 1, 0, + 0, 1, + 1, 1 }; + + glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, av); + glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, at); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glBindTexture(GL_TEXTURE_2D, 0); +} + +QT_END_NAMESPACE + +} |