每次更新仅调用一次 Canvas onPaint?

Invoke Canvas onPaint exactly once per update?

又名:Canvas requestPaint() 太慢; requestAnimationFrame() 太快了

我正在尝试创建一个尽可能快地重绘的 QML Canvas——在主 UI 渲染循环中每次更新一次——以创建一个 FPS 计时器。

我最初写了这个简单的测试:

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)
    }
}

我只收到一次回调。所以我添加了 requestPaint():

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

没有变化:我仍然只收到一个回调。如果我使用 markDirty(),也一样。如果我真的在 canvas 每个回调上画一些东西,也是一样的。

所以我搬到了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)
        }
    }
}

现在我收到回电,但太多了。平均而言,我每毫秒收到 77 个回调,有时在一毫秒内收到多达 127 个回调。如此多的回调,以至于应用程序中的任何其他内容都不会显示,甚至最初也不显示。即使我删除了 console.log(),以证明我不受 i/o 约束。

如何让我的 canvas 重绘一次 "per frame",以便我可以半准确地测量 FPS?为什么 requestPaint() 实际上不起作用?为什么 requestAnimationFrame() 显然没用?

你的方法的问题是你正在请求 onPaint 的绘画,这是行不通的, 因为 onPaint 事件是从内部触发的 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();
        }
    }
}

在此调用期间,d->polishScheduled 设置为 true,如果您再次调用 requestPaint(),则不会发生任何事情。您需要异步触发它。例如,使用间隔为 0 的 Timer

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()
  }
}

另一个Timer在这里记录fps。当我 运行 在 qmlscene 中使用此代码时,我得到 60 fps。

Qt 5.9 之前有 bug with requestAnimationFrame()。此错误已修复。

以下代码按预期和期望的方式工作,以保持 canvas 不断重绘:

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