Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3DImage and SharpDX flickering on slow hardware

I am using the SharpDX.WPF project for the WPF abilities, it seems like an easy to understand low-overhead library, compared to the Toolkit that comes with SharpDX (which has the same issue!)

First: I fixed the SharpDX.WPF project for the latest SharpDX using the following: https://stackoverflow.com/a/19791534/442833

Then I made the following hacky adjustment to DXElement.cs, a solution that was also done here:

private Query queryForCompletion;
    public void Render()
    {
        if (Renderer == null || IsInDesignMode)
            return;

        var test = Renderer as D3D11;
        if (queryForCompletion == null)
        {

            queryForCompletion = new Query(test.Device,
                new QueryDescription {Type = QueryType.Event, Flags = QueryFlags.None});
        }

        Renderer.Render(GetDrawEventArgs());

        Surface.Lock();
        test.Device.ImmediateContext.End(queryForCompletion);
        // wait until drawing completes
        Bool completed;
        var counter = 0;
        while (!(test.Device.ImmediateContext.GetData(queryForCompletion, out completed)
                 && completed))
        {
            Console.WriteLine("Yielding..." + ++counter);
            Thread.Yield();
        }
        //Surface.Invalidate();
        Surface.AddDirtyRect(new Int32Rect(0, 0, Surface.PixelWidth, Surface.PixelHeight));
        Surface.Unlock();
    }

Then I render 8000 cubes in a cube pattern...

Yielding...

gets printed to the console quite often, but the flickering is still there. I am assuming that WPF is nice enough to show the image using a different thread before the rendering is done, not sure though... This same issue also happens when I use the Toolkit variant of WPF support with SharpDX.

Images to demonstate the issue:

  • Bad
  • Better
  • Almost
  • Intended

Note: It randomly switches between these old images, randomly. I am also using really old hardware which makes the flickering much more appearant (GeForce Quadro FX 1700)

A made a repo which contains the exact same source-code as I am using to get this issue: https://github.com/ManIkWeet/FlickeringIssue/

like image 503
ManIkWeet Avatar asked Feb 11 '23 09:02

ManIkWeet


2 Answers

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.

enter image description here

like image 57
Glenn Slayden Avatar answered Feb 14 '23 04:02

Glenn Slayden


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....

like image 34
Jeroen van Langen Avatar answered Feb 14 '23 05:02

Jeroen van Langen