Visualization modes for rendering.
authorGunnar Sletta <gunnar.sletta@digia.com>
Sat, 7 Dec 2013 08:41:44 +0000 (09:41 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 29 Jan 2014 19:10:07 +0000 (20:10 +0100)
There are two ways of setting this right now. One is to set it on
startup using an environment varible.

QSG_VISUALIZE=

"batches" - Visualize batchtes in the renderer. Merged batches are
drawn with solid color and Unmerged batches are drawn with a diagonal
line pattern. Few unique colors means good batching. Unmerged batches
are bad if they contain many individual nodes.

"clip" - Visualize clipping as red areas on top of the scene.

"overdraw" - Visualize all items in 3D to highlight overdraws. This mode
can also be used to detect geometry outside the viewport to some
extent. Opaque items are rendered with a green tint while translucent
items are rendered with a red tint. The bounding box for the viewport
is rendered in blue.  Opaque content is easier for the scenegraph to
process and it can also be faster to render on some hardware.

"changes" - Changes in the scenegraph are visualized with a flashing
overlay with a random color. Changes on a primitive is visualized with
a solid color while changes in an ancestor, such as a matrix or
opacity changes is visualized with a pattern.

The second way to set the visualization mode is to set it at runtime
through QString QQuickWindowPrivate::customRenderMode. This "API" is
string based so it is not tied to the batch renderer and in theory can
support other custom renderers.

The visualized elements do not respect clipping and rendering order
is arbitrary.

Change-Id: I31efbe53fc905145bf48080ede3e36945cb60dcf
Reviewed-by: Michael Brasser <michael.brasser@live.com>
src/quick/items/qquickwindow.cpp
src/quick/items/qquickwindow_p.h
src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
src/quick/scenegraph/coreapi/qsgrenderer_p.h
src/quick/scenegraph/scenegraph.pri
src/quick/scenegraph/scenegraph.qrc
src/quick/scenegraph/shaders/visualization.frag [new file with mode: 0644]
src/quick/scenegraph/shaders/visualization.vert [new file with mode: 0644]

index a311971..6778cf1 100644 (file)
@@ -344,6 +344,7 @@ void QQuickWindowPrivate::syncSceneGraph()
         mode |= QSGRenderer::ClearColorBuffer;
     renderer->setClearMode(mode);
 
+    renderer->setCustomRenderMode(customRenderMode);
     context->endSync();
 }
 
@@ -418,6 +419,7 @@ void QQuickWindowPrivate::init(QQuickWindow *c)
     contentItemPrivate->windowRefCount = 1;
     contentItemPrivate->flags |= QQuickItem::ItemIsFocusScope;
 
+    customRenderMode = qgetenv("QSG_VISUALIZE");
     windowManager = QSGRenderLoop::instance();
     QSGContext *sg = windowManager->sceneGraphContext();
     context = windowManager->createRenderContext(sg);
index d5c7b5d..944a320 100644 (file)
@@ -57,6 +57,7 @@
 #include "qquickwindow.h"
 
 #include <QtQuick/private/qsgcontext_p.h>
+#include <private/qsgbatchrenderer_p.h>
 
 #include <QtCore/qthread.h>
 #include <QtCore/qmutex.h>
@@ -200,6 +201,7 @@ public:
 
     QSGRenderContext *context;
     QSGRenderer *renderer;
+    QByteArray customRenderMode; // Default renderer supports "clip", "overdraw", "changes", "batches" and blank.
 
     QSGRenderLoop *windowManager;
     QQuickAnimatorController *animationController;
index 1a9669f..9fbf6d8 100644 (file)
 #include "qsgbatchrenderer_p.h"
 #include <private/qsgshadersourcebuilder_p.h>
 
+#include <QQuickWindow>
+
+#include <qmath.h>
+
 #include <QtCore/QElapsedTimer>
 
 #include <QtGui/QGuiApplication>
@@ -305,6 +309,9 @@ void Updater::updateStates(QSGNode *n)
             qDebug() << " - forceupdate";
     }
 
+    if (Q_UNLIKELY(renderer->m_visualizeMode == Renderer::VisualizeChanges))
+        renderer->visualizeChangesPrepare(sn);
+
     visitNode(sn);
 }
 
@@ -760,6 +767,7 @@ Renderer::Renderer(QSGRenderContext *ctx)
     , m_currentClip(0)
     , m_currentClipType(NoClip)
     , m_vao(0)
+    , m_visualizeMode(VisualizeNothing)
 {
     setNodeUpdater(new Updater(this));
 
@@ -1668,7 +1676,7 @@ void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData,
     *indexCount += iCount;
 }
 
-const QMatrix4x4 &Renderer::matrixForRoot(Node *node)
+static QMatrix4x4 qsg_matrixForRoot(Node *node)
 {
     if (node->type() == QSGNode::TransformNodeType)
         return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
@@ -2004,7 +2012,7 @@ void Renderer::renderMergedBatch(const Batch *batch)
     // 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);
+        m_current_model_view_matrix = qsg_matrixForRoot(batch->root);
     else
         m_current_model_view_matrix.setToIdentity();
     m_current_determinant = m_current_model_view_matrix.determinant();
@@ -2133,7 +2141,7 @@ void Renderer::renderUnmergedBatch(const Batch *batch)
     char *iOffset = indexBase + batch->vertexCount * gn->geometry()->sizeOfVertex();
 #endif
 
-    QMatrix4x4 rootMatrix = batch->root ? matrixForRoot(batch->root) : QMatrix4x4();
+    QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(batch->root) : QMatrix4x4();
 
     while (e) {
         gn = e->node;
@@ -2358,7 +2366,6 @@ void Renderer::render()
     cleanupBatches(&m_opaqueBatches);
     cleanupBatches(&m_alphaBatches);
 
-
     if (m_rebuild & BuildBatches) {
         prepareOpaqueBatches();
         prepareAlphaBatches();
@@ -2408,6 +2415,9 @@ void Renderer::render()
 
     m_rebuild = 0;
 
+    if (m_visualizeMode != VisualizeNothing)
+        visualize();
+
     if (m_vao)
         m_vao->release();
 }
@@ -2446,7 +2456,7 @@ void Renderer::renderRenderNode(Batch *batch)
     QMatrix4x4 matrix;
     while (xform != rootNode()) {
         if (xform->type() == QSGNode::TransformNodeType) {
-            matrix = matrixForRoot(e->root) * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
+            matrix = qsg_matrixForRoot(e->root) * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
             break;
         }
         xform = xform->parent();
@@ -2510,6 +2520,312 @@ void Renderer::renderRenderNode(Batch *batch)
 
 }
 
+class VisualizeShader : public QOpenGLShaderProgram
+{
+public:
+    int color;
+    int matrix;
+    int rotation;
+    int tweak;
+};
+
+void Renderer::visualizeDrawGeometry(const QSGGeometry *g)
+{
+    if (g->attributeCount() < 1)
+        return;
+    const QSGGeometry::Attribute *a = g->attributes();
+    glVertexAttribPointer(0, a->tupleSize, a->type, false, g->sizeOfVertex(), g->vertexData());
+    if (g->indexCount())
+        glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
+    else
+        glDrawArrays(g->drawingMode(), 0, g->vertexCount());
+
+}
+
+void Renderer::visualizeBatch(Batch *b)
+{
+    VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+
+    if (b->positionAttribute != 0)
+        return;
+
+    QSGGeometryNode *gn = b->first->node;
+    QSGGeometry *g = gn->geometry();
+    const QSGGeometry::Attribute &a = g->attributes()[b->positionAttribute];
+
+    glBindBuffer(GL_ARRAY_BUFFER, b->vbo.id);
+
+    QMatrix4x4 matrix(m_current_projection_matrix);
+    if (b->root)
+        matrix = matrix * qsg_matrixForRoot(b->root);
+
+    QRect viewport = viewportRect();
+    shader->setUniformValue(shader->tweak, viewport.width(), viewport.height(), b->merged ? 0 : 1, 0);
+
+    QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0);
+    float cr = color.redF();
+    float cg = color.greenF();
+    float cb = color.blueF();
+    shader->setUniformValue(shader->color, cr, cg, cb, 1.0);
+
+    if (b->merged) {
+        shader->setUniformValue(shader->matrix, matrix);
+        for (int ds=0; ds<b->drawSets.size(); ++ds) {
+            const DrawSet &set = b->drawSets.at(ds);
+            glVertexAttribPointer(a.position, 2, a.type, false, g->sizeOfVertex(), (void *) (qintptr) (set.vertices));
+            glDrawElements(g->drawingMode(), set.indexCount, GL_UNSIGNED_SHORT, (void *) (qintptr) (b->vbo.data + set.indices));
+        }
+    } else {
+        Element *e = b->first;
+        int offset = 0;
+        while (e) {
+            gn = e->node;
+            g = gn->geometry();
+            shader->setUniformValue(shader->matrix, matrix * *gn->matrix());
+            glVertexAttribPointer(a.position, a.tupleSize, a.type, false, g->sizeOfVertex(), (void *) (qintptr) offset);
+            glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
+            offset += g->sizeOfVertex() * g->vertexCount();
+            e = e->nextInBatch;
+        }
+    }
+}
+
+
+
+
+void Renderer::visualizeClipping(QSGNode *node)
+{
+    if (node->type() == QSGNode::ClipNodeType) {
+        VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+        QSGClipNode *clipNode = static_cast<QSGClipNode *>(node);
+        QMatrix4x4 matrix = m_current_projection_matrix;
+        if (clipNode->matrix())
+            matrix = matrix * *clipNode->matrix();
+        shader->setUniformValue(shader->matrix, matrix);
+        visualizeDrawGeometry(clipNode->geometry());
+    }
+
+    QSGNODE_TRAVERSE(node) {
+        visualizeClipping(child);
+    }
+}
+
+#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \
+                              | QSGNode::DirtyOpacity \
+                              | QSGNode::DirtyMatrix \
+                              | QSGNode::DirtyNodeRemoved)
+
+void Renderer::visualizeChangesPrepare(Node *n, uint parentChanges)
+{
+    uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
+    uint selfDirty = n->dirtyState | parentChanges;
+    if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
+        m_visualizeChanceSet.insert(n, selfDirty);
+    SHADOWNODE_TRAVERSE(n) {
+        visualizeChangesPrepare(*child, childDirty);
+    }
+}
+
+void Renderer::visualizeChanges(Node *n)
+{
+
+    if (n->type() == QSGNode::GeometryNodeType && m_visualizeChanceSet.contains(n)) {
+        uint dirty = m_visualizeChanceSet.value(n);
+        bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0;
+
+        VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+        QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 0.3, 1.0);
+        float ca = 0.5;
+        float cr = color.redF() * ca;
+        float cg = color.greenF() * ca;
+        float cb = color.blueF() * ca;
+        shader->setUniformValue(shader->color, cr, cg, cb, ca);
+
+        QRect viewport = viewportRect();
+        shader->setUniformValue(shader->tweak, viewport.width(), viewport.height(), tinted ? 0.5 : 0, 0);
+
+        QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
+
+        QMatrix4x4 matrix = m_current_projection_matrix;
+        if (n->element()->batch->root)
+            matrix = matrix * qsg_matrixForRoot(n->element()->batch->root);
+        matrix = matrix * *gn->matrix();
+        shader->setUniformValue(shader->matrix, matrix);
+        visualizeDrawGeometry(gn->geometry());
+
+        // This is because many changes don't propegate their dirty state to the
+        // parent so the node updater will not unset these states. They are
+        // not used for anything so, unsetting it should have no side effects.
+        n->dirtyState = 0;
+    }
+
+    SHADOWNODE_TRAVERSE(n) {
+        visualizeChanges(*child);
+    }
+}
+
+void Renderer::visualizeOverdraw_helper(Node *node)
+{
+    if (node->type() == QSGNode::GeometryNodeType) {
+        VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+        QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
+
+        QMatrix4x4 matrix = m_current_projection_matrix;
+        matrix(2, 2) = m_zRange;
+        matrix(2, 3) = 1.0f - node->element()->order * m_zRange;
+
+        if (node->element()->batch->root)
+            matrix = matrix * qsg_matrixForRoot(node->element()->batch->root);
+        matrix = matrix * *gn->matrix();
+        shader->setUniformValue(shader->matrix, matrix);
+
+        QColor color = node->element()->batch->isOpaque ? QColor::fromRgbF(0.3, 1.0, 0.3) : QColor::fromRgbF(1.0, 0.3, 0.3);
+        float ca = 0.33;
+        shader->setUniformValue(shader->color, color.redF() * ca, color.greenF() * ca, color.blueF() * ca, ca);
+
+        visualizeDrawGeometry(gn->geometry());
+    }
+
+    SHADOWNODE_TRAVERSE(node) {
+        visualizeOverdraw_helper(*child);
+    }
+}
+
+void Renderer::visualizeOverdraw()
+{
+    VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+    shader->setUniformValue(shader->color, 0.5, 0.5, 1, 1);
+
+    QRect viewport = viewportRect();
+    shader->setUniformValue(shader->tweak, viewport.width(), viewport.height(), 0, 1);
+
+    glBlendFunc(GL_ONE, GL_ONE);
+
+    static float step = 0;
+    step += M_PI * 2 / 1000.;
+    if (step > M_PI * 2)
+        step = 0;
+    float angle = 80.0 * sin(step);
+
+    QMatrix4x4 xrot; xrot.rotate(20, 1, 0, 0);
+    QMatrix4x4 zrot; zrot.rotate(angle, 0, 0, 1);
+    QMatrix4x4 tx; tx.translate(0, 0, 1);
+
+    QMatrix4x4 m;
+
+//    m.rotate(180, 0, 1, 0);
+
+    m.translate(0, 0.5, 4);
+    m.scale(2, 2, 1);
+
+    m.rotate(-30, 1, 0, 0);
+    m.rotate(angle, 0, 1, 0);
+    m.translate(0, 0, -1);
+
+    shader->setUniformValue(shader->rotation, m);
+
+    float box[] = {
+        // lower
+        -1, 1, 0,   1, 1, 0,
+        -1, 1, 0,   -1, -1, 0,
+        1, 1, 0,    1, -1, 0,
+        -1, -1, 0,  1, -1, 0,
+
+        // upper
+        -1, 1, 1,   1, 1, 1,
+        -1, 1, 1,   -1, -1, 1,
+        1, 1, 1,    1, -1, 1,
+        -1, -1, 1,  1, -1, 1,
+
+        // sides
+        -1, -1, 0,  -1, -1, 1,
+        1, -1, 0,   1, -1, 1,
+        -1, 1, 0,   -1, 1, 1,
+        1, 1, 0,    1, 1, 1
+    };
+    glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, box);
+    glLineWidth(2);
+    glDrawArrays(GL_LINES, 0, 24);
+
+    visualizeOverdraw_helper(m_nodes.value(rootNode()));
+
+    // Animate the view...
+    QSurface *surface = QOpenGLContext::currentContext()->surface();
+    if (surface->surfaceClass() == QSurface::Window)
+        if (QQuickWindow *window = qobject_cast<QQuickWindow *>(static_cast<QWindow *>(surface)))
+            window->update();
+}
+
+void Renderer::setCustomRenderMode(const QByteArray &mode)
+{
+    if (mode.isEmpty()) m_visualizeMode = VisualizeNothing;
+    else if (mode == "clip") m_visualizeMode = VisualizeClipping;
+    else if (mode == "overdraw") m_visualizeMode = VisualizeOverdraw;
+    else if (mode == "batches") m_visualizeMode = VisualizeBatches;
+    else if (mode == "changes") m_visualizeMode = VisualizeChanges;
+}
+
+void Renderer::visualize()
+{
+    if (!m_shaderManager->visualizeProgram) {
+        VisualizeShader *prog = new VisualizeShader();
+        QSGShaderSourceBuilder::initializeProgramFromFiles(
+            prog,
+            QStringLiteral(":/scenegraph/shaders/visualization.vert"),
+            QStringLiteral(":/scenegraph/shaders/visualization.frag"));
+        prog->bindAttributeLocation("v", 0);
+        prog->link();
+        prog->bind();
+        prog->color = prog->uniformLocation("color");
+        prog->tweak = prog->uniformLocation("tweak");
+        prog->matrix = prog->uniformLocation("matrix");
+        prog->rotation = prog->uniformLocation("rotation");
+        m_shaderManager->visualizeProgram = prog;
+    } else {
+        m_shaderManager->visualizeProgram->bind();
+    }
+    VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
+
+    glDisable(GL_DEPTH_TEST);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+    glEnableVertexAttribArray(0);
+
+    // Blacken out the actual rendered content...
+    float bgOpacity = 0.8;
+    if (m_visualizeMode == VisualizeBatches)
+        bgOpacity = 1.0;
+    float v[] = { -1, 1,   1, 1,   -1, -1,   1, -1 };
+    shader->setUniformValue(shader->color, 0, 0, 0, bgOpacity);
+    shader->setUniformValue(shader->matrix, QMatrix4x4());
+    shader->setUniformValue(shader->rotation, QMatrix4x4());
+    shader->setUniformValue(shader->tweak, 0, 0, 0, 0);
+    glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, v);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+    if (m_visualizeMode == VisualizeBatches) {
+        srand(0); // To force random colors to be roughly the same every time..
+        for (int i=0; i<m_opaqueBatches.size(); ++i) visualizeBatch(m_opaqueBatches.at(i));
+        for (int i=0; i<m_alphaBatches.size(); ++i) visualizeBatch(m_alphaBatches.at(i));
+    } else if (m_visualizeMode == VisualizeClipping) {
+        QRect viewport = viewportRect();
+        shader->setUniformValue(shader->tweak, viewport.width(), viewport.height(), 0.5, 0);
+        shader->setUniformValue(shader->color, 0.2, 0, 0, 0.2);
+        visualizeClipping(rootNode());
+    } else if (m_visualizeMode == VisualizeChanges) {
+        visualizeChanges(m_nodes.value(rootNode()));
+        m_visualizeChanceSet.clear();
+    } else if (m_visualizeMode == VisualizeOverdraw) {
+        visualizeOverdraw();
+    }
+
+    // Reset state back to defaults..
+    glDisable(GL_BLEND);
+    glDisableVertexAttribArray(0);
+    shader->release();
+}
+
 QT_END_NAMESPACE
 
 }
index 0aa84da..d22ab40 100644 (file)
@@ -368,7 +368,7 @@ public:
         float lastOpacity;
     };
 
-    ShaderManager() : blitProgram(0) { }
+    ShaderManager() : blitProgram(0), visualizeProgram(0) { }
     ~ShaderManager() {
         qDeleteAll(rewrittenShaders.values());
         qDeleteAll(stockShaders.values());
@@ -385,6 +385,7 @@ public:
     QHash<QSGMaterialType *, Shader *> stockShaders;
 
     QOpenGLShaderProgram *blitProgram;
+    QOpenGLShaderProgram *visualizeProgram;
 };
 
 class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer
@@ -393,6 +394,14 @@ public:
     Renderer(QSGRenderContext *);
     ~Renderer();
 
+    enum VisualizeMode {
+        VisualizeNothing,
+        VisualizeBatches,
+        VisualizeClipping,
+        VisualizeChanges,
+        VisualizeOverdraw
+    };
+
 protected:
     void nodeChanged(QSGNode *node, QSGNode::DirtyState state);
     void preprocess() Q_DECL_OVERRIDE;
@@ -448,6 +457,16 @@ private:
     inline Batch *newBatch();
     void invalidateAndRecycleBatch(Batch *b);
 
+    void visualize();
+    void visualizeBatch(Batch *b);
+    void visualizeClipping(QSGNode *node);
+    void visualizeChangesPrepare(Node *n, uint parentChanges = 0);
+    void visualizeChanges(Node *n);
+    void visualizeOverdraw();
+    void visualizeOverdraw_helper(Node *node);
+    void visualizeDrawGeometry(const QSGGeometry *g);
+    void setCustomRenderMode(const QByteArray &mode);
+
     QSet<Node *> m_taggedRoots;
     QDataBuffer<Element *> m_opaqueRenderList;
     QDataBuffer<Element *> m_alphaRenderList;
@@ -484,6 +503,9 @@ private:
 
     // For minimal OpenGL core profile support
     QOpenGLVertexArrayObject *m_vao;
+
+    QHash<Node *, uint> m_visualizeChanceSet;
+    VisualizeMode m_visualizeMode;
 };
 
 Batch *Renderer::newBatch()
index 43811e6..296d6e2 100644 (file)
@@ -132,6 +132,8 @@ public:
     void setClearMode(ClearMode mode) { m_clear_mode = mode; }
     ClearMode clearMode() const { return m_clear_mode; }
 
+    virtual void setCustomRenderMode(const QByteArray &) { };
+
 Q_SIGNALS:
     void sceneGraphChanged(); // Add, remove, ChangeFlags changes...
 
index 6868e10..570d6b9 100644 (file)
@@ -160,5 +160,7 @@ OTHER_FILES += \
     $$PWD/shaders/textmask_core.vert \
     $$PWD/shaders/texture_core.frag \
     $$PWD/shaders/vertexcolor_core.frag \
-    $$PWD/shaders/vertexcolor_core.vert
+    $$PWD/shaders/vertexcolor_core.vert \
+    scenegraph/shaders/visualization.frag \
+    scenegraph/shaders/visualization.vert
 
index 2be8b24..e6a90c9 100644 (file)
@@ -64,5 +64,7 @@
         <file>shaders/texture_core.frag</file>
         <file>shaders/vertexcolor_core.frag</file>
         <file>shaders/vertexcolor_core.vert</file>
+        <file>shaders/visualization.vert</file>
+        <file>shaders/visualization.frag</file>
     </qresource>
 </RCC>
diff --git a/src/quick/scenegraph/shaders/visualization.frag b/src/quick/scenegraph/shaders/visualization.frag
new file mode 100644 (file)
index 0000000..205b726
--- /dev/null
@@ -0,0 +1,11 @@
+uniform lowp vec4 color;
+uniform mediump vec4 tweak; // x,y -> width, height; z -> intensity of ;
+
+varying mediump vec2 pos;
+
+void main(void)
+{
+    lowp vec4 c = color;
+    c.xyz += pow(max(sin(pos.x + pos.y), 0.0), 2.0) * tweak.z * 0.1;
+    gl_FragColor = c;
+}
diff --git a/src/quick/scenegraph/shaders/visualization.vert b/src/quick/scenegraph/shaders/visualization.vert
new file mode 100644 (file)
index 0000000..f1892b7
--- /dev/null
@@ -0,0 +1,22 @@
+attribute highp vec4 v;
+uniform highp mat4 matrix;
+uniform highp mat4 rotation;
+
+// w -> apply 3d rotation and projection
+uniform lowp vec4 tweak;
+
+varying mediump vec2 pos;
+
+void main()
+{
+    vec4 p = matrix * v;
+
+    if (tweak.w > 0.0) {
+        vec4 proj = rotation * p;
+        gl_Position = vec4(proj.x, proj.y, 0, proj.z);
+    } else {
+        gl_Position = p;
+    }
+
+    pos = v.xy * 1.37;
+}