D3DImage and SharpDX flickering on slow hardware

别来无恙 提交于 2019-12-03 16:53:31

I think you are not locking properly. As far as I understand the MSDN documentation you are supposed to lock during the entire rendering not just at the end of it:

While the D3DImage is locked, your application can also render to the Direct3D surface assigned to the back buffer.

The information you find on the net about D3DImage/SharpDX is somewhat confusing because the SharpDX guys don't really like the way D3DImage is implemented (can't blame them), so there are statements about this being a "bug" on Microsofts side when its actually just improper usage of the API.

Yes, locking during rendering has performance issues, but it is probably not possible to fix them without porting WPF to DirectX11 and implementing something like a SwapChainPanel which is available in UWP apps. (WPF itself still runs on DirectX9)

If the locking is a performance issue for you, one idea I had (but never tested) is that you could render to an offscreen surface and reduce the lock duration to copying that surface over to the D3DImage. No idea if that would help performance wise but its something to try.

Related to D3DImage locking, note that the D3DImage.TryLock API has rather unconventional semantics which most developers would not expect:

Beware!
You must call Unlock even in the case where TryLock indicates failure (i.e., returns false)

Although perhaps more of an alarming design choice than a bug per se, misunderstanding this behavior will trivially result in D3DImage deadlocks and hangs, and thus might be responsible for much of the frustration people experience in attempting to get D3DImage working properly.

The following code is a correct WPF D3D render with no flicker in my app:

void WPF_D3D_render(IntPtr pSurface)
{
    if (TryLock(new Duration(default(TimeSpan))))
    {
        SetBackBuffer(D3DResourceType.IDirect3DSurface9, pSurface);
        AddDirtyRect(new Int32Rect(0, 0, PixelWidth, PixelHeight));
    }
    Unlock();    //  <--- !
}

Yes, this unintuitive code is actually correct; it is the case that that D3DImage.TryLock(0) leaks one internal D3D buffer lock every time it returns failure. You don't have to take my word for it, here's the CLR code from PresentationCore.dll v4.0.30319:

private bool LockImpl(Duration timeout)
{
    bool flag = false;

    if (_lockCount == uint.MaxValue)
        throw new InvalidOperationException();

    if (_lockCount == 0)
    {
        if (timeout == Duration.Forever)
            flag = _canWriteEvent.WaitOne();
        else
            flag = _canWriteEvent.WaitOne(timeout.TimeSpan, false);

        UnsubscribeFromCommittingBatch();
    }
    _lockCount++;
    return flag;
}

Notice that the internal _lockCount field is incremented regardless of whether the function returns success or failure. You have to call Unlock() yourself, as shown in the first code example above, if you want to avoid certain deadlock. Failing to do so creates is nasty to debug, too, because the component won't (potentially) deadlock until the next render pass, by which time the relevant evidence is long gone.

The unusual behavior does not seem to be mentioned at MSDN, but to be fair, that documentation doesn't note that you have to call Unlock() if the call is successful, either.

The problem is not the Locking mechanism. Normally you use Present to draw to present the image. Present will wait until all drawing is ready. With D3DImage you are not using the Present() method. Instead of Presenting, you lock, adding a DirtyRect and unlock the D3DImage.

The rendering is done asynchrone so when you are unlocking, the draw actions might not be ready. This is causing the flicker effect. Sometimes you see items half drawn. A poor solution (i've tested with) is adding a small delay before unlocking. It helped a little, but it wasn't a neat solution. It was terrible!

Solution:

I continued with something else; I was expirimenting with MSAA (antialiasing) and the first problem I faced was; MSAA cannot be done on the dx11/dx9 shared texture, so i decided to render to a new texture (dx11) and create a copy to the dx9 shared texture. I slammed my head on the tabel, because now it was anti-aliased AND flicking-free!! Don't forget to call Flush() before adding a dirty rect.

So, creating a copy of the texture: DXDevice11.Device.ImmediateContext.ResolveSubresource(_dx11RenderTexture, 0, _dx11BackpageTexture, 0, ColorFormat); (_dx11BackpageTexture is shared texture) will wait until the rendering is ready and will create a copy.

This is how I got rid of the flickering....

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!