5

我正在尝试在 WPF 应用程序中通过 SharpDX 使用 DirectX11 在窗口中平滑滚动一些图像。

一点背景:

图像是信号返回,随时间变化,已从文件加载并作为纹理数组加载到 D3D 中,图像本身被渲染为一系列带纹理的四边形(一系列相邻的矩形在一条很长的水平线上)。视口的设置使左右边缘代表时间偏移,并且在这些时间之间的时间段内显示信号。DirectX 的实现非常简单;四边形顶点生成一次,一个非常小的每帧缓冲区包含一个简单的 2D 世界视图变换,根据当前可见范围/缩放等更新比例和平移。据我所知,D3D 实现不是我遇到的问题的一部分-它确实是一个非常简单的设置,并且似乎渲染得非常快(应该如此),

问题:

为可见时间范围设置动画时,图像的滚动不流畅。图像在很多时候“抖动”,坦率地说看起来很糟糕(当它不抖动时,它看起来很棒)。

设置:

DirectX 被渲染为 D3DImage,在幕后进行了一些工作以使 DX11 工作 - 此代码取自https://sharpdxwpf.codeplex.com/

有多个 D3DImages(最多 4 个),排列在一个网格中(我一直在测试两个,两个都包含相同时间段的信号并且一起动画)。

这些 D3DImage 由 DrawingVisual(由自定义 FrameworkElement 托管)绘制,它们用作 ImageBrush 的源。frameworkelement 对象根据需要触发渲染,绘图视觉对象处理 D3D 渲染调用并绘制一个矩形以使用 D3DImage 画笔填充控件。

时间范围值使用 WPF DoubleAnimation 进行动画处理。当前显示的视觉效果通过 INotifyPropertyChanged 绑定到此值,并在每次更改时触发渲染(通过 InvalidateVisual)。

DrawingVisual 渲染代码,由“位置”值的变化触发(可见时间范围的开始):

// update scene per-frame buffer first, with world-view transform
using (DrawingContext dc = RenderOpen()
{
    _scene.Renderer.Render(_draw_args);
    _image.Invalidate();

    dc.DrawRectangle(_brush, null, new Rect(viewer.RenderSize));
}

我试过的:

我已经尝试了很多事情(并进行了很多搜索)来尝试确定问题是否是由于渲染请求时间抖动,或者问题是否进一步进入渲染过程。

  1. 挂钩 CompositionTarget.Rendering 首先通过 CompositionTarget.Rendering 驱动位置值的更新,并让其余过程保持原样(即元素对该值的变化作出反应):

(不用说,这是非常“测试”的代码):

Stopwatch rsw;
long last_time = 0;
void play()
{
    rsw = new Stopwatch();
    last_time = 0;
    rsw.Start();
    CompositionTarget.Rendering += rendering;
}

void rendering(object sender, EventArgs e)
{
    double update_distance = rsw.ElapsedMilliseconds - last_time;
    Position += 10 * update_distance;
    last_time = rsw.ElapsedMilliseconds;
}

结果 - 比简单的双重动画差。

  1. 使用 DispatcherTimer。与上面类似,使用计时器结合秒表来更好地判断经过的时间。更糟糕的结果再次出现,有趣的是 CPU 使用率从大约 7%(半个核心)下降到 0.7%。

最初的尝试以及变体 1 和 2 都尝试更新单个值,然后触发相关方的渲染。出于兴趣,我记录了渲染请求到达绘图视觉对象时的时间差异——尽管 DisptacherTimer 实际上在那里给出了最一致的结果(WPF 动画和 CompositionTarget 都丢弃了奇数帧),但 DisptacherTimer 也给出了最紧张的动画。

  1. 更新图像,但不使视觉无效。更新 ImageBrush 的源会更新显示的图像,而无需重新渲染 DrawingVisual。这种方法产生了非常紧张的结果。

  2. 框架元素本身中的 CompositionTarget.Rendering。这产生了该批次的最佳结果,但仍不完美。抖动会发生,然后消散,然后又回来了。在这种方法中,保存 DV 的框架元素与 CompositionTarget.Rendering 挂钩,并且视觉对象会查询当前位置,该位置正在独立进行动画处理。我几乎可以接受这种方法。

  3. 尝试等待直到渲染 D3D 场景,然后再使图像无效(没有明显的改进):

    _scene.Renderer.Render(_draw_args);
    _scene.Renderer.Device.ImmediateContext.End(q);

    while (!(_scene.Renderer.Device.ImmediateContext.IsDataAvailable(q))) Thread.Yield();

    _image.Invalidate();

观察:

  1. 我真的不认为这是一个性能问题。我的开发机器有一个很好的显卡,8 个 i7 内核等,这是一个简单的渲染操作。
  2. 这看起来确实像是 D3D 和 WPF 渲染之间的某种同步问题,但我不知道如何开始研究这个问题。
  3. 如果我有两个并行动画的图像,则抖动在两个图像中的第一个上更加明显(通常)。
  4. 如果我在制作动画时主动调整窗口大小,动画会非常流畅(尽管正在进行大量额外工作,因为 D3D 上下文正在不断调整大小)。

编辑

我已经尽可能地收回事情以尝试隔离问题,并且该问题似乎是 WPF 更新和呈现 D3DImage 的方式的基础。

我已经修改了 SharpDX Samples 解决方案中的 WPFHost 示例,以便显示的简单三角形在屏幕上显示动画。此示例托管在由 CompositionTarget.Rendering 上的 DPFCanvas 呈现的 DX10ImageSource 中。

这个例子再简单不过了,在 WPF 中渲染 D3DImage 时,它​​几乎是“接近金属”的。一个三角形,通过渲染之间的时间差计算的值在屏幕上平移。口吃仍然存在,来来去去,好像某种同步问题。它让我感到困惑,但本质上使 SharpDX 在 WPF 中无法用于任何类型的流畅动画,这非常令人失望。

如果有人有兴趣重现此问题,请在此处获得 SharpDX 示例:https ://github.com/sharpdx/SharpDX-Samples

我对 WPFHost 示例进行了以下简单更改:

void IScene.Render()
{
...

        EffectMatrixVariable wv = this.SimpleEffect.GetVariableBySemantic("WorldView").AsMatrix();
        wv.SetMatrix(this.WorldViewMatrix);

...
}

void IScene.Update(TimeSpan sceneTime)
{
    float x = (float)sceneTime.Milliseconds * 0.001f - 0.5f;
    WorldViewMatrix = Matrix.Translation(x, 0, 0);
}

在着色器 Simple.fx 中:

float4x4 WorldViewTransform : WorldView;

PS_IN VS( VS_IN input )
{
    PS_IN output = (PS_IN)0;

    output.pos = mul(input.pos, WorldViewTransform);
    output.col = input.col * Overlay;

    return output;
}
4

1 回答 1

0

D3DImage 从根本上被破坏了

如果您不需要在 D3D 之上覆盖 XAML 元素,或者您不需要过于频繁地调整 D3D 表面的大小,则最好将 HWND/WinForm 托管到您的 WPF 应用程序中并使用常规渲染循环直接渲染到它。如果您想了解有关原因的更多详细信息,可以查看此问题

于 2014-12-17T13:18:35.850 回答