D3DImage和SharpDX在慢速硬件上闪烁

时间:2014-12-08 12:33:23

标签: wpf multithreading flicker sharpdx d3dimage

我使用SharpDX.WPF项目获得WPF功能,与SharpDX附带的Toolkit相比,它似乎是一个易于理解的低开销库(具有相同的问题!)

首先:我使用以下内容修复了最新SharpDX的SharpDX.WPF项目:https://stackoverflow.com/a/19791534/442833

然后我对DXElement.cs进行了以下hacky调整,这个解决方案也完成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();
    }

然后我以立方体模式渲染8000个立方体......

  

...屈服

经常被打印到控制台,但闪烁仍然存在。 我假设WPF足够好,在渲染完成之前使用不同的线程显示图像,但不确定... 当我使用支持SharpDX的WPF支持的Toolkit变种时,也会发生同样的问题。

图片展示了这个问题:

注意:随机地在这些旧图像之间随机切换。我也使用非常古老的硬件,使闪烁更具吸引力(GeForce Quadro FX 1700)

创建了一个repo,其中包含与我用来解决此问题完全相同的源代码: https://github.com/ManIkWeet/FlickeringIssue/

3 个答案:

答案 0 :(得分:3)

D3DImage锁定相关,请注意D3DImage.TryLock API具有相当非常规的语义,大多数开发人员都不会期望:

  

<强> 小心!
  即使tests表示失败(即返回 false ),您也必须致电Unlock

虽然可能更多的是警告设计选择而不是本身,但误解这种行为将导致D3DImage死锁并挂起,因此可能会造成很大责任人们在试图让D3DImage正常工作时遇到的挫折感。

以下代码是正确的 WPF D3D渲染,我的应用中没有闪烁:

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

是的,这个不直观的代码实际上是正确的;情况是D3DImage.TryLock(0) 每次返回失败时都会泄漏一个内部D3D缓冲区锁。你不必接受我的话,这里是来自PresentationCore.dll v4.0.30319的CLR代码:

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

请注意,无论函数是返回成功还是失败,内部_lockCount字段都会递增。如果你想避免某些死锁,你必须自己调用Unlock(),如上面的第一个代码示例所示。如果不这样做,那么调试也是令人讨厌的,因为组件在下一次渲染过程之前不会(可能)死锁,到那时相关证据已经很久了。

TryLock似乎没有提及异常行为,但公平地说,文档并未注意到如果呼叫成功,您还必须致电Unlock()

  

MSDN

答案 1 :(得分:1)

问题不在于锁定机制。通常使用Present绘制来呈现图像。 Present将等待所有绘图准备就绪。使用D3DImage,您没有使用Present()方法。您可以锁定,添加DirtyRect并解锁D3DImage,而不是呈现。

渲染完成异步,因此当您解锁时,绘制操作可能没有准备好。这会导致闪烁效应。有时您会看到一半画出的物品。一个糟糕的解决方案(我已经测试过)在解锁之前添加了一个小延迟。它有点帮助,但它不是一个简洁的解决方案。太可怕了!

<强>解决方案:

我继续其他的事情;我正在使用MSAA (抗锯齿)进行评估,我遇到的第一个问题是;无法在dx11 / dx9共享纹理上完成MSAA,因此我决定渲染到新纹理(dx11)并创建dx9共享纹理的副本。我把头撞在了表格上,因为现在它已经消除了锯齿并轻弹了!在添加脏矩形之前,不要忘记调用Flush()

因此,创建纹理的副本:DXDevice11.Device.ImmediateContext.ResolveSubresource(_dx11RenderTexture, 0, _dx11BackpageTexture, 0, ColorFormat); _dx11BackpageTexture是共享纹理)将等待渲染准备就绪并创建副本。

这就是我摆脱闪烁的方式......

答案 2 :(得分:0)

我认为你没有正确锁定。据我所知,MSDN documentation你应该在整个渲染过程中锁定,而不仅仅是在它结束时:

  

当D3DImage被锁定时,您的应用程序也可以渲染到分配给后台缓冲区的Direct3D表面。

你在网上找到的关于D3DImage / SharpDX的信息有些令人困惑,因为SharpDX的人并不喜欢D3DImage的实现方式(不能责怪他们),所以有关于这个的说法a&#34; bug&#34;在微软方面,当它实际上只是不恰当地使用API​​时。

是的,渲染期间的锁定存在性能问题,但如果不将WPF移植到DirectX11并实现UWP应用程序中可用的SwapChainPanel之类的东西,则可能无法修复它们。 (WPF本身仍在DirectX9上运行)

如果锁定对您来说是一个性能问题,我有一个想法(但从未测试过)是您可以渲染到屏幕外表面并减少锁定持续时间以将该表面复制到D3DImage。不知道这是否有助于提高性能,但需要尝试。