使用 SharpDX 在 WPF 中滚动图像时出现生涩动画
Posted
技术标签:
【中文标题】使用 SharpDX 在 WPF 中滚动图像时出现生涩动画【英文标题】:Jerky animation when scrolling image in WPF using SharpDX 【发布时间】:2015-02-15 06:01:00 【问题描述】:我正在尝试在 WPF 应用程序中通过 SharpDX 使用 DirectX11 在窗口中平滑滚动一些图像。
一点背景:
图像是信号返回,随时间变化,已从文件加载并作为纹理数组加载到 D3D 中,图像本身被渲染为一系列带纹理的四边形(一系列相邻的矩形长水平线)。 视口的设置使左右边缘代表时间偏移,并且在这些时间之间的时间段内显示信号。 DirectX 的实现非常简单;四边形顶点生成一次,一个非常小的每帧缓冲区包含一个简单的 2D 世界视图变换,该变换根据当前可见范围/缩放等更新比例和平移。 据我所知,D3D 实现不是我遇到的问题的一部分——它确实是一个非常简单的设置,而且渲染速度似乎非常快(应该如此),尽管我确实有一些更高级的东西(根据需要从磁盘流式传输纹理),我已禁用所有这些,并在尝试解决我遇到的问题时使用非常简单(小)的纹理运行..
问题:
在可见时间范围设置动画时,图像的滚动不流畅。图像在很多时候“抖动”,坦率地说看起来很糟糕(当它不抖动时,它看起来很棒)。
设置:
DirectX 被渲染为 D3DImage,在幕后进行了一些工作以使 DX11 正常工作 - 此代码取自 https://sharpdxwpf.codeplex.com/
有多个 D3DImage(最多 4 个),排列在一个网格中(我一直在测试两个,它们都包含相同时间段的信号并且一起动画)。
这些 D3DImage 由 DrawingVisual(由自定义 FrameworkElement 托管)绘制,它们用作 ImageBrush 的源。 frameworkelement 对象根据需要触发渲染,绘图视觉处理 D3D 渲染调用并绘制一个矩形以使用 D3DImage 画笔填充控件。
时间范围值使用 WPF DoubleAnimation 进行动画处理。当前显示的视觉效果通过 INotifyPropertyChanged 绑定到该值,并在每次更改时触发渲染(通过 InvalidateVisual)。
DrawingVisual 渲染代码,由“Position”值的变化触发(可见时间范围的开始):
// 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));
我尝试过的:
我已经尝试了很多方法(并进行了很多搜索)来尝试确定问题是由于渲染请求时间抖动引起的,还是问题进一步进入了渲染过程。
-
连接到 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;
结果 - 比简单的双动画差。
-
使用 DispatcherTimer。与上面类似,使用计时器结合秒表来更好地判断经过的时间。更糟糕的结果再次出现,有趣的是 CPU 使用率从大约 7%(半个核心)下降到 0.7%。
最初的尝试以及变体 1 和 2 都尝试更新单个值,然后触发相关方的呈现。出于兴趣,我记录了渲染请求到达绘图视觉对象时的时间差异——尽管 DisptacherTimer 实际上在那里给出了最一致的结果(WPF 动画和 CompositionTarget 都丢弃了奇数帧),但 DisptacherTimer 也给出了最紧张的动画。
更新图像,但不使视觉无效。更新 ImageBrush 的源会更新显示的图像,而无需重新渲染 DrawingVisual。这种方法产生了非常不稳定的结果。
CompositionTarget.Rendering 在框架元素本身中。这产生了该批次的最佳结果,但仍不完美。抖动会发生,然后消散,然后又回来了。在这种方法中,保存 DV 的框架元素与 CompositionTarget.Rendering 挂钩,并且视觉对象会查询当前位置,该位置正在独立进行动画处理。我几乎可以接受这种方法。
尝试等待直到渲染 D3D 场景,然后再使图像无效(没有明显的改进):
_scene.Renderer.Render(_draw_args); _scene.Renderer.Device.ImmediateContext.End(q);
while (!(_scene.Renderer.Device.ImmediateContext.IsDataAvailable(q))) 线程.Yield();
_image.Invalidate();
观察:
-
我真的不认为这是一个性能问题。我的开发机器有一个不错的显卡、8 个 i7 内核等,这是一个简单的渲染操作。
这看起来确实像是 D3D 和 WPF 渲染之间的某种同步问题,但我不知道如何开始调查。
如果我有两个并行动画图像,则抖动在两个图像中的第一个上更加明显(通常)。
如果我在制作动画时主动调整窗口大小,动画会非常流畅(尽管由于 D3D 上下文不断调整大小,因此需要做很多额外的工作)。
编辑
我已尽可能回过头来尝试隔离问题,并且该问题似乎是 WPF 更新和呈现 D3DImage 的方式的基础。
我已经修改了 SharpDX 示例解决方案中的 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);
在着色器 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;
【问题讨论】:
【参考方案1】:D3DImage 从根本上损坏了。
如果您不需要在 D3D 之上覆盖 XAML 元素,或者您不需要过于频繁地调整 D3D 表面的大小,最好将 HWND/WinForm 托管到您的 WPF 应用程序中并使用常规渲染循环直接渲染到它.如果您想详细了解原因,可以查看issue。
【讨论】:
哎呀——这不是我希望的答复! (但无论如何还是谢谢你)。看起来确实是这样,如果甚至不可能平滑地为单个三角形设置动画并且没有已知的解决方法。我实际上正在升级一个相当大的 WPF 应用程序以使用 SharpDX,并丢失所有覆盖能力将受到严重限制 - 我将调查该路线的合理性,否则我们可能不得不忍受口吃或回到 DrawingVisual 方法(实际上更顺畅,但在其他方面受到限制)。以上是关于使用 SharpDX 在 WPF 中滚动图像时出现生涩动画的主要内容,如果未能解决你的问题,请参考以下文章
使用嵌套适配器垂直滚动时出现滞后问题 | IGListKit