QML Canvas.requestAnimationFrame explodes

1.6k views Asked by At

I am trying to use QML Canvas.requestAnimationFrame to draw some custom animation. I expected that the provided callback is called once for each frame, about 60 times per second. The code I have is:

Canvas {
    id: canvas

    width: 600
    height: 600

    function draw() {
    }

    Component.onCompleted: {
        var i = 1;

        function drawFrame() {
            requestAnimationFrame(drawFrame)
            console.log("Frame callback: " + i++)
            draw()
        }

        drawFrame()
    }

    onPaint: {
        draw()
    }

}

What I see is that the callback is called way more often. The counter reaches 70000 in a few seconds, after which the application becomes entirely unresponsive.

What am I doing wrong?

3

There are 3 answers

0
Phrogz On BEST ANSWER

There was a bug with requestAnimationFrame() prior to Qt 5.9. This bug has been fixed.

This code works as expected and desired to keep the canvas continuously redrawing.

Canvas {
    width:100; height:100;
    property var ctx
    onAvailableChanged: if (available) ctx = getContext('2d');
    onPaint: {
        if (!ctx) return;
        ctx.clearRect(0, 0, width, height);
        // draw here
        requestAnimationFrame(paint);
    }
}
2
qCring On

Your drawFrame() function passes itself as callback function for rendering and you're caught in a loop. You either want to render on demand only like for example after user input to keep resources at a minimum, or you have some logic that changes every frame or you just need continuous rendering.

If time-based rendering is what you want, just use a Timer:

import QtQuick 2.4

Canvas  {
    id: cvs
    width: 600; height: 600
    contextType: "2d"
    property real i : 0

    onPaint: {
        console.timeEnd("t")
        if (context) {
            context.clearRect (0, 0, width, height)
            context.fillRect(i, 50, 50, 50 + i)
        }
        console.time("t")
    }

    Timer {
        interval: 1
        repeat: true
        running: true

        onTriggered: {
            cvs.i = (cvs.i + 0.1) % cvs.width
            cvs.requestPaint()
        }
    }
}

Edit:

Just updated the code:

onPaint calls are synced to the display frame rate even though the timer interval is set to 1ms as can be seen from the log when running the sample above. In fact the whole block assigned to the onTriggered signal is executed every millisecond but requestPaint() makes sure to synchronize rendering calls for best performance just like requestAnimationFrame() does for the HTML canvas.

Apparently, requestAnimationFrame() inside the QML.Canvas doesn't work as expected and there's not much documentation...

Hope this helps!

0
olgierdh On

Just a small update on this topic. I've encountered same problem with the Qt qml Canvas and requestAnimationFrame while I was working on my project. The solution I've found is to switch the render strategy to Threaded and use onPainted signal. The example of qCring's code with my updates looks like this:

import QtQuick 2.4

Canvas  {
    id: cvs
    width: 600; height: 600

    //renderStrategy: Canvas.Cooperative // Will work as well but animation chops on my computer from time to time
    renderStrategy: Canvas.Threaded

    contextType: "2d"
    property real i : 0

    function animate() {
        cvs.i = (cvs.i + 0.1) % cvs.width;
    }

    onPaint: {
        console.timeEnd( "t" )
        if ( context ) {
            context.clearRect( 0, 0, width, height )
            context.fillRect( i, 50, 50, 50 + i )
        }
        console.time("t")

        cvs.requestAnimationFrame(animate);
    }

    onPainted: {
        cvs.requestPaint();
    }
}