4

我正在使用 SharpDX.WPF 项目来实现 WPF 功能,与 SharpDX 附带的工具包相比,它似乎是一个易于理解的低开销库(它有同样的问题!)

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

然后我对 DXElement.cs 进行了以下 hacky 调整,这里也完成了一个解决方案:

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)

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

4

3 回答 3

5

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

谨防!即使在TryLock指示失败的情况下(即返回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.dllv4.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()如果调用成功,您也必须调用。

在此处输入图像描述

于 2016-09-19T20:39:53.270 回答
2

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

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

解决方案:

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

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

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

于 2016-11-11T14:12:44.557 回答
1

我认为你没有正确锁定。据我了解MSDN 文档,您应该在整个渲染过程中锁定,而不仅仅是在它的末尾:

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

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

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

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

于 2016-09-18T09:34:53.047 回答