How to determine when a WPF control was redrawn?

I use D3DImage to display a sequence of frames that are displayed on the same Direct3D surface one after another. My current logic is this:

  • Display last displayed frame (i.e.D3DImage.Lock () / AddDirtyRect () / Unlock ())
  • Start Rendering Next Frame
  • Wait until the next frame is ready, and so that it displays it
  • Show last displayed frame
  • ...

The problem with this approach is that when we finished calling Unlock () in D3DImage, the image was not actually copied, it should only be copied in the next WPF rendering. Therefore, perhaps we will create a new frame on the surface of Direct3D before WPF can display it. The end result is that we see the missing frames on the display.

I'm currently experimenting with using a separate Direct3D texture to render and make a copy of the “display texture” just before the display, which gives better results, but carries significant overhead. It would be preferable to just know when the D3DImage will be updated and immediately begin rendering the next frame. Is this possible, if so, how? Or do you have an idea best?

Thanks.

+4
source share
2 answers

It seems like a clean way to do this to render in parallel with the user interface is to render onto a separate D3D surface and copy it onto the display surface (i.e. passed to SetBackBuffer) between calls to block () and unlock (). Thus, the algorithm becomes:

  • Copy and display the last processed frame, i.e.
    • Lock()
    • Copy from rendering to display surface
    • SetBackBuffer(displaySurface)
    • AddDirtyRect()
    • Unlock()
  • Schedule a new render on the rendering surface
  • Wait for it to finish and that time is OK to display it
  • Go to 1

The documentation for D3DImage explicitly indicates :

Do not update Direct3D surface when D3DImage is unlocked.

The big point here is the copy, which is potentially expensive (i.e.> 2 ms if the hardware is busy). To use the display surface when D3DImage is unlocked (avoiding the potentially costly operation during rendering), you would have to resort to disassembly and reflection in order to connect to your own D3DImage rendering ...

0
source

The CompositionTarget.Rendering event is fired when rendering WPF, so when you have to do your Lock() and Unlock() , after Unlock() you can fire up the next render.

You should also check RenderingTime , because an event can fire several times per frame. Try something like this:

 private void HandleWpfCompositionTargetRendering(object sender, EventArgs e) { RenderingEventArgs rea = e as RenderingEventArgs; // It 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 for comments

Are you sure there is a race condition? This page says that the back buffer is copied when Unlock() called.

And if there really is a race condition, how about installing Lock / Unlock around the render code? This page says that Lock() will lock until copying is complete.

0
source

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


All Articles