【问题标题】:D3DImage and SharpDX flickering on slow hardwareD3DImage 和 SharpDX 在慢速硬件上闪烁
【发布时间】:2015-02-06 02:47:41
【问题描述】:

我正在使用 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 足以在渲染完成之前使用不同的线程显示图像,但不确定...... 当我将 WPF 支持的 Toolkit 变体与 SharpDX 一起使用时,也会发生同样的问题。

说明问题的图片:

注意:它在这些旧图像之间随机切换,随机。我还在使用非常旧的硬件,这使得闪烁更加明显(GeForce Quadro FX 1700)

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

【问题讨论】:

标签: wpf multithreading flicker sharpdx d3dimage


【解决方案1】:

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

当心!
即使TryLock 表示失败,您也必须调用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(),如上面的第一个代码示例所示。如果不这样做,调试起来也很麻烦,因为组件在下一次渲染之前不会(可能)死锁,到那时相关证据早已不复存在。

MSDN 似乎没有提到异常行为,但公平地说,该文档也没有指出如果调用成功,您也必须调用 Unlock()

【讨论】:

    【解决方案2】:

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

    渲染是异步完成的,所以当您解锁时,绘制动作可能还没有准备好。这导致了闪烁效果。有时你会看到项目被画了一半。 (我已经测试过) 一个糟糕的解决方案是在解锁之前添加一个小延迟。它有一点帮助,但它不是一个很好的解决方案。太可怕了!

    解决方案:

    我继续做其他事情;我正在使用 MSAA (抗锯齿),我遇到的第一个问题是; MSAA 无法在 dx11/dx9 共享纹理上完成,所以我决定渲染到一个新纹理 (dx11) 并为 dx9 共享纹理创建一个副本。我把头撞在桌子上,因为现在它是抗锯齿和无闪烁的!!在添加脏矩形之前不要忘记致电Flush()

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

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

    【讨论】:

    • 你有这个方法的工作示例吗?
    【解决方案3】:

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

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

    您在网上找到的有关 D3DImage/SharpDX 的信息有些令人困惑,因为 SharpDX 家伙并不真正喜欢 D3DImage 的实现方式(不能怪他们),所以有人说这是一个“错误”当它实际上只是对 API 的不当使用时,在微软方面。

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

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

    【讨论】:

    • 我已经开始使用 DirectX11,它的实现方式要好得多。即使我没有尝试过,我也会将您的答案标记为解决方案。
    • @Zarat 哇,在提供我自己的stackoverflow.com/questions/13368940/… 之前,我真的没有看到你的答案,看起来我们都围绕着同一个想法..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-21
    • 2015-03-04
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多