1

Motivation: I currently have 1000 simple items in my QML scene, and one of them animates at 60fps, so the entire scene repaints at 60fps. Cpu usage is on average 15% on each of my 4 virtual cores on my pc. On the target hardware the situation is even worse - 60% on each of the 4 physical cores, leading to overheating, leading to freeze. Note that I have implemented an optimization: via Loaders, unload all items that are outside the (scrolling) viewport (so, only ~18 items are loaded at any given time). The perf stats I report are with this optimization - without it it's worse.

My solution is to start drawing all the 1000 items in a single QQuickFramebufferObject, and stop having them as actual QML Item's. That way I'll avoid Qt punishing me for just having 1000 (unloaded!) items.

Where I'm stuck though: How to draw the text parts of the items in OpenGL?

Approach 1: I know QPainter can be used to directly render text into a QOpenGLWidget, but that option seems to be absent in QQFBO.

Approach 2: Have a single, parentless Text item in QML with layer.enabled: true, set its text property, wait 1 frame (for it to render) then fetch the texture. Somewhat ugly and roundabout; also may be slow-ish.

Approach 3: Look at the source of QQuickText to see what magic it does and copy it. Might be difficult, and I'll have to comply with license restrictions.

Approach 4: Do software rendering with a QPainter to a QImage, then upload that image to a texture. Elegant, but may be too slow.

Any suggestions on a way to do it that doesn't have the problems in these approaches?

Stefan Monov
  • 11,332
  • 10
  • 63
  • 120
  • Have you looked at the output from QML profiler to see what's taking up so much processing time? – Kuba hasn't forgotten Monica May 30 '17 at 22:03
  • @KubaOber: Yes I have; the time spent in QML is insignificant. So I ran the app in a C++ profiler (CodeXL) and found that the vast majority of time is spent in the videocard's driver, for whatever weird reason. This happened both in Windows and in Linux. And it happened both when I use my AMD videocard and when I use my Intel videocard (I have switchable graphics). – Stefan Monov May 30 '17 at 22:31
  • 1
    A C++ profiler won't be able to figure out that time is spent in a driver. What particular functions is the time actually spent on? – Kuba hasn't forgotten Monica May 31 '17 at 04:14
  • @KubaOber: Weird, this time it's giving me different results. The majority of time is spent in `QSGRootNode::~QSGRootNode()`, due to it calling `QSGNodeUpdater::isNodeBlocked(QSGNode*, QSGNode*) const`. I've looked at the Qt source but haven't been able to figure out why it's even calling the former. I'll post a testcase in another question and will give you a link. – Stefan Monov Jun 02 '17 at 13:25
  • @KubaOber: Please check out [this question](https://stackoverflow.com/q/44330231) for a testcase. – Stefan Monov Jun 02 '17 at 13:34

1 Answers1

0

It's not totally clear why rendering one item makes your whole scene repaint. But if only one item is animating you might want to split your scene. Those not moving should be into a parent item, and the one moving can be outside.

There is a rather easy way to render a subtree to a FBO, just render the subtree to a ShaderItem which does nothing.

This example renders an Image with a grayscale shader (borrowed from the example at Qt docs):

import QtQuick 2.0

Rectangle {
    width: 200; height: 100
    Row {
        Image { id: img;
                sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
        ShaderEffect {
            width: 100; height: 100
            property variant src: img
            vertexShader: "
                uniform highp mat4 qt_Matrix;
                attribute highp vec4 qt_Vertex;
                attribute highp vec2 qt_MultiTexCoord0;
                varying highp vec2 coord;
                void main() {
                    coord = qt_MultiTexCoord0;
                    gl_Position = qt_Matrix * qt_Vertex;
                }"
            fragmentShader: "
                varying highp vec2 coord;
                uniform sampler2D src;
                uniform lowp float qt_Opacity;
                void main() {
                    lowp vec4 tex = texture2D(src, coord);
                    gl_FragColor = vec4(vec3(dot(tex.rgb,
                                        vec3(0.344, 0.5, 0.156))),
                                             tex.a) * qt_Opacity;
                }"
        }
    }
}

A ShaderEffect is just a render to texture, you are seeing a rectangle filled with a picture of the object. In this case the illusion is still there, but your animated object is only dealing with a single textured rectangle.

I don't know if that is the solution as it seems the problem might be elsewhere. Please elaborate your problem and I might update the answer as needed.

I know this seems like your second approach but in this case you render to texture your whole unchanged subtree. If I may guess it seems you have scrolling text batching again and again to the GPU because some scrolling animation, if you use a ShaderEffect and a long stripe of items you could animate just the scrolling window and always leave your text static, avoiding batching.

Ariel M.
  • 896
  • 8
  • 24