Search code examples
qtqmlqt-quick

Invoke Canvas onPaint exactly once per update?


AKA: Canvas requestPaint() too slow; requestAnimationFrame() too fast

I'm trying to create a QML Canvas that repaints as fast as possible—once per update in the main UI render loop—in order to create an FPS timer.

I initially wrote this simple test:

import QtQuick 2.7
import QtQuick.Window 2.2

Window {
    visible:true; width:100; height:100
    Canvas {
        anchors.fill:parent
        onPaint: console.log(+new Date)
    }
}

I only get the callback once. So I added requestPaint():

onPaint: {
    console.log(+new Date)
    requestPaint()
}

No change: I still only get one callback. Same if I use markDirty(). Same if I actually paint something on the canvas each callback.

So I moved to requestAnimationFrame():

import QtQuick 2.7
import QtQuick.Window 2.2
Window {
    visible:true; width:100; height:100
    Canvas {
        anchors.fill:parent
        Component.onCompleted: crank()
        function crank(){
            console.log(+new Date)
            requestAnimationFrame(crank)
        }
    }
}

Now I get callbacks, but way too many. On average, I get 77 callbacks per millisecond, some times as many as 127 callbacks in a single millisecond. So many callbacks that nothing else in the application displays, not even initially. Even if I remove the console.log(), to prove that I'm not i/o bound).

How can I get my canvas to repaint once "per frame", so that I can measure the FPS semi-accurately? Any why does requestPaint() not actually work? And why is requestAnimationFrame() apparently useless?


Solution

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

    The following 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);
        }
    }