Call Canvas onPaint exactly once per update?

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

I am trying to create a QML canvas that replicates as quickly as possible - once per update in the main UI rendering loop - to create an FPS timer.

I originally 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 get a callback only once. So I added requestPaint() :

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

No change: I get only one callback. Same thing if I use markDirty() . Same thing if I actually draw something on the canvas every callback.

So, I went 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 too many. On average, I get 77 callbacks per millisecond, several times more than 127 callbacks per millisecond. So many callbacks that nothing is displayed in the application, not even initially. Even if I remove console.log() to prove that I am not attached to i / o).

How can I get my canvas to recolor once per frame so that I can accurately measure FPS? Anyone why requestPaint() actually doesn't work? And why does requestAnimationFrame() seem useless?

0
source share
2 answers

Prior to Qt 5.9, an error occurred with requestAnimationFrame() . This bug has been fixed.

The following code works as expected and is desirable for continuous canvas rewinding:

 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); } } 
0
source

The problem with your approach is that you are requesting paint from onPaint , this will not work, because the onPaint event is onPaint from within QQuickItem::polish()

 void QQuickItem::polish() { Q_D(QQuickItem); if (!d->polishScheduled) { d->polishScheduled = true; if (d->window) { QQuickWindowPrivate *p = QQuickWindowPrivate::get(d->window); bool maybeupdate = p->itemsToPolish.isEmpty(); p->itemsToPolish.append(this); if (maybeupdate) d->window->maybeUpdate(); } } } 

During this call, d->polishScheduled set to true, and if you call requestPaint() again, nothing happens. You must activate it asynchronously. For example, use a Timer with an interval of 0.

 import QtQuick 2.0 Canvas { id: canvas width: 200 height: 200 property real angle property int fps Timer { id: repaintTimer running: false interval: 0 onTriggered: { angle += 0.01 canvas.requestPaint() } } Timer { interval: 1000 running: true repeat: true onTriggered: { console.log(fps) fps = 0 } } onPaint: { var ctx = getContext("2d") ctx.save() ctx.clearRect(0, 0, width, height) ctx.moveTo(100, 100) ctx.translate(100,100) ctx.rotate(angle) ctx.beginPath() ctx.lineTo(40, 10) ctx.lineTo(40, 40) ctx.lineTo(10, 40) ctx.lineTo(10, 10) ctx.closePath() ctx.stroke() ctx.restore() fps += 1 repaintTimer.start() } } 

Another Timer here for fps recording. When I run this code in qmlscene , I get 60 frames per second.

+2
source

Source: https://habr.com/ru/post/988725/


All Articles