简体   繁体   English

如何检测何时重绘WPF控件?

[英]How to detect when a WPF control has been redrawn?

I am using D3DImage to display a sequence of frames that are rendered unto the same Direct3D Surface one after the other. 我正在使用D3DImage显示一系列帧,这些帧一个接一个地呈现在同一个Direct3D Surface中。 My current logic is thus: 我现在的逻辑是这样的:

  • Display last rendered frame (ieD3DImage.Lock()/AddDirtyRect()/Unlock()) 显示最后渲染的帧(ieD3DImage.Lock()/ AddDirtyRect()/ Unlock())
  • Start rendering next frame 开始渲染下一帧
  • Wait for next frame to be ready and that it's time to display it 等待下一帧准备就绪,是时候显示它了
  • Display last rendered frame 显示上次渲染的帧
  • ... ...

The problem with this approach is that when we are done calling Unlock() on D3DImage, the image isn't actually copied, it's only scheduled to be copied on the next WPF render. 这种方法的问题在于,当我们在D3DImage上调用Unlock()时,图像实际上没有被复制,它只被安排在下一个WPF渲染上复制。 It's therefore possible that we render a new frame on the Direct3D surface before WPF has had the chance to display it. 因此,在WPF有机会显示之前,我们可能会在Direct3D表面上渲染一个新帧。 The net result is that we see missed frames on the display. 最终结果是我们在显示器上看到错过的帧。

Right now I'm experimenting with using a separate Direct3D texture for rendering and performing a copy to a "display texture" just before display, which is giving better results but incurs substantial overhead. 现在我正在尝试使用单独的Direct3D纹理进行渲染,并在显示之前执行复制到“显示纹理”,这样可以提供更好的结果,但会产生大量的开销。 It would be preferrable to just be able to know when D3DImage is done refreshing and start rendering the next frame immediately after. 最好能够知道D3DImage何时完成刷新并立即开始渲染下一帧。 Is this possible, if so how? 这有可能,如果是这样的话怎么样? Or do you have a better idea altogether? 或者你有一个更好的想法?

Thanks. 谢谢。

The CompositionTarget.Rendering event is called when WPF is going to render, so that's when you should do your Lock() and Unlock() . 当WPF要渲染时,会调用CompositionTarget.Rendering事件 ,因此应该执行Lock()Unlock() After the Unlock() , you can kick off the next render. Unlock() ,您可以启动下一个渲染。

You should also check the RenderingTime because the event may fire multiple times per frame. 您还应该检查RenderingTime因为事件可能每帧触发多次。 Try something like this: 尝试这样的事情:

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

Update to address comments 更新以解决评论

Are you sure that there's a race condition? 你确定有竞争条件吗? This page says that the back buffer gets copied when you call Unlock() . 此页面表示在调用Unlock()时会复制后台缓冲区。

And if there really is a race condition, how about putting Lock/Unlock around the render code? 如果确实存在竞争条件,那么在渲染代码周围放置锁定/解锁怎么样? This page says that Lock() will block until the copy is finished. 此页面表示Lock()将阻止,直到副本完成。

It looks like the clean way of doing this, in order to render in parallel with the UI, is to render into a separate D3D surface, and copy it to the display surface (ie the one passed to SetBackBuffer) between the calls to Lock() and Unlock(). 为了与UI并行渲染,看起来干净的方式是渲染到单独的D3D表面,并将其复制到锁定调用之间的显示表面(即传递给SetBackBuffer的表面)( )和解锁()。 So the algorithm becomes: 所以算法变成:

  1. Copy and display the last rendered frame, ie 复制并显示最后渲染的帧,即
    • Lock()
    • Copy from render to display surface 从渲染复制到显示表面
    • SetBackBuffer(displaySurface)
    • AddDirtyRect()
    • Unlock()
  2. Schedule a new render to the render surface 将新渲染计划到渲染表面
  3. Wait for it to complete and that the timing is ok to display it 等待它完成,并且时间可以显示它
  4. Goto 1 转到1

The documentation for D3DImage explicitely states : D3DImage的文档明确指出

Do not update the Direct3D surface while the D3DImage is unlocked. 在D3DImage解锁时不要更新Direct3D表面。

The sore point here is the copy, which is potentially costly (ie >2ms if the hardware is busy). 这里的痛点是副本,这可能是昂贵的(即如果硬件繁忙则> 2ms)。 In order to use the display surface while the D3DImage is unlocked (avoiding a potentially costly operation at render time), one would have to resort to disassembly and reflection to hook into D3DImage's own rendering... 为了在D3DImage解锁时使用显示器表面(避免在渲染时可能进行昂贵的操作),人们不得不求助于反汇编和反射来挂钩到D3DImage自己的渲染中......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM