使用SharpDX在WPF中滚动图像时生涩的动画

Mat*_*att 5 wpf directx-11 managed-directx sharpdx

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

一点背景:

图像是信号返回,随时间变化,从文件加载并作为纹理数组加载到D3D中,图像本身呈现为一系列纹理四边形(一系列相邻的矩形,在很长的水平线上) ).设置视口,使左右边缘表示时间偏移,并且信号显示在这些时间之间的时间段内.DirectX实现非常简单; 四个顶点生成一次,一个非常小的每帧缓冲区包含一个简单的2D世界视图变换,根据当前可见范围/缩放等更新缩放和平移.据我所知,D3D实现不是我遇到的问题的一部分 - 它确实是一个非常简单的设置,并且似乎渲染非常快(应该如此),虽然我确实有一些更好的东西(根据需要从磁盘传输纹理),但我有禁用所有这些并运行非常简单(小)纹理,而我尝试解决我遇到的问题..

问题:

当可见时间范围被动画化时,图像的滚动不平滑.图像在很多时候"抖动",坦率地看起来很糟糕(当它不抖动时,它看起来很棒).

设置:

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

有多个D3DImages(最多总共4个),排列在一个网格中(我已经测试了两个,两个都包含相同时间段的信号并一起动画).

这些D3DImages由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));
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的:

我已经尝试过很多东西(并经过大量搜索)来尝试确定问题是由于抖动渲染请求计时,还是问题是否进一步进入渲染过程.

  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;
}
Run Code Online (Sandbox Code Playgroud)

结果 - 比简单的双动画更糟糕.

  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示例,以便在屏幕上显示所显示的简单三角形.此示例托管在DX10ImageSource中,该源由CompositionTarget.Rendering上的DPFCanvas呈现.

这个例子不可能更简单,并且在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);
}
Run Code Online (Sandbox Code Playgroud)

并在着色器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;
}
Run Code Online (Sandbox Code Playgroud)

xoo*_*ofx 0

D3DImage 从根本上被破坏了

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