5

我正在使用 D3DImage 显示一系列帧,这些帧一个接一个地渲染到同一个 Direct3D Surface。因此,我目前的逻辑是:

  • 显示最后渲染的帧(即D3DImage.Lock()/AddDirtyRect()/Unlock())
  • 开始渲染下一帧
  • 等待下一帧准备好,是时候显示它了
  • 显示最后渲染的帧
  • ...

这种方法的问题在于,当我们在 D3DImage 上调用 Unlock() 后,实际上并没有复制图像,它只是计划在下一次 WPF 渲染时复制。因此,我们有可能在 WPF 有机会显示它之前在 Direct3D 表面上渲染一个新帧。最终结果是我们在显示器上看到了丢失的帧。

现在,我正在尝试使用单独的 Direct3D 纹理进行渲染,并在显示之前将其复制到“显示纹理”,这会产生更好的结果,但会产生大量开销。最好能够知道 D3DImage 何时完成刷新并立即开始渲染下一帧。这可能吗,如果可以,怎么办?或者你有更好的主意吗?

谢谢。

4

2 回答 2

2

CompositionTarget.Rendering 事件在 WPF 将要渲染时被调用,所以此时您应该执行Lock()and Unlock(). 之后Unlock(),您可以开始下一个渲染。

您还应该检查,RenderingTime因为该事件可能每帧触发多次。尝试这样的事情:

private void HandleWpfCompositionTargetRendering(object sender, EventArgs e)
{
    RenderingEventArgs rea = e as RenderingEventArgs;

    // It's possible for Rendering to call back twice in the same frame
    // so only render when we haven't already rendered in this frame.
    if (this.lastRenderTime == rea.RenderingTime)
        return;

    if (this.renderIsFinished)
    {
        // Lock();
        // SetBackBuffer(...);
        // AddDirtyRect(...);
        // Unlock();

        this.renderIsFinished = false;
        // Fire event to start new render
        // the event needs to set this.renderIsFinished = true when the render is done

        // Remember last render time
        this.lastRenderTime = rea.RenderingTime;
    }
}

更新以解决评论

你确定有比赛条件吗?这个页面说当你调用时后台缓冲区被复制Unlock()

如果真的存在竞争条件,那么在渲染代码周围放置 Lock/Unlock 怎么样?该页面表示Lock()将阻止直到复制完成。

于 2013-05-10T16:36:19.610 回答
0

看起来这样做的干净方法是为了与 UI 并行渲染,是渲染到一个单独的 D3D 表面,然后在调用 Lock( ) 和解锁()。于是算法变为:

  1. 复制并显示最后渲染的帧,即
    • Lock()
    • 从渲染复制到显示表面
    • SetBackBuffer(displaySurface)
    • AddDirtyRect()
    • Unlock()
  2. 为渲染表面安排新的渲染
  3. 等待它完成并且时间可以显示它
  4. 转到 1

D3DImage 的文档明确指出

不要在 D3DImage 解锁时更新 Direct3D 表面。

这里的痛点是副本,它可能很昂贵(即如果硬件繁忙,则>2ms)。为了在 D3DImage 解锁时使用显示表面(避免在渲染时进行潜在的昂贵操作),必须借助反汇编和反射来挂钩 D3DImage 自己的渲染......

于 2013-05-13T19:48:02.820 回答