我正在遇到一个奇怪的问题,试图使用WPF渲染多条折线(在2300x1024 Canvas中,64条折线各约400-500个顶点)。折线每50ms更新一次。
由于某种原因,我的应用程序UI变得非常迟缓,几乎没有响应用户输入。
我正在使用以下类来避免在显示时更新点集合:
class DoubleBufferPlot
{
/// <summary>
/// Double-buffered point collection
/// </summary>
private readonly PointCollection[] mLineBuffer =
{
new PointCollection(),
new PointCollection()
};
private int mWorkingBuffer; //index of the workign buffer (buffer being modified)
#region Properties
//Polyline displayed
public Polyline Display { get; private set; }
/// <summary>
/// index operator to access points
/// </summary>
/// <param name="aIndex">index</param>
/// <returns>Point at aIndex</returns>
public Point this[int aIndex]
{
get { return mLineBuffer[mWorkingBuffer][aIndex]; }
set { mLineBuffer[mWorkingBuffer][aIndex] = value; }
}
/// <summary>
/// Number of points in the working buffer
/// </summary>
public int WorkingPointCount
{
get { return mLineBuffer[mWorkingBuffer].Count; }
set
{
SetCollectionSize(mLineBuffer[mWorkingBuffer], value);
}
}
#endregion
public DoubleBufferPlot(int numPoints = 0)
{
Display = new Polyline {Points = mLineBuffer[1]};
if (numPoints > 0)
{
SetCollectionSize(mLineBuffer[0], numPoints);
SetCollectionSize(mLineBuffer[1], numPoints);
}
}
/// <summary>
/// Swap working and display buffer
/// </summary>
public void Swap()
{
Display.Points = mLineBuffer[mWorkingBuffer]; //display workign buffer
mWorkingBuffer = (mWorkingBuffer + 1) & 1; //swap
//adjust buffer size if needed
if (Display.Points.Count != mLineBuffer[mWorkingBuffer].Count)
{
SetCollectionSize(mLineBuffer[mWorkingBuffer], Display.Points.Count);
}
}
private static void SetCollectionSize(IList<Point> collection, int newSize)
{
while (collection.Count > newSize)
{
collection.RemoveAt(collection.Count - 1);
}
while (collection.Count < newSize)
{
collection.Add(new Point());
}
}
}
我在屏幕外更新工作缓冲区,然后调用Swap()来显示它。所有64条折线(DoubleBufferPlot.Display)都作为子项添加到Canvas中。
我使用Visual Studio Concurrency Analyzer工具查看发生了什么,并发现每次更新后主线程花费46ms执行一些与WPF相关的任务:System.Widnows.ContextLayoutManager.UpdateLayout()和System.Windows.Media.MediaContex .Render()。
我还发现还有另一个线程正在运行几乎不间断的渲染 wpfgfx_v0400.dll!CPartitionThread :: ThreadMain ... wpfgfx_v0400.dll!CDrawingContext ::渲染 ... 等
我在WPF上阅读了很多文章,包括:Can WPF render a line path with 300,000 points on it in a performance-sensitive environment? 还有这篇文章http://msdn.microsoft.com/en-us/magazine/dd483292.aspx。
我(或我的公司)试图避免使用DrawingVisual,因为项目的其余部分使用WPF形状API。
知道为什么这么慢?我甚至尝试过禁用抗锯齿(RenderOptions.SetEdgeMode(mCanvas,EdgeMode.Aliased)),但它没有多大帮助。
为什么布局更新需要这么长时间。谁是WPF内部专家?
非常感谢你。
答案 0 :(得分:0)
在尝试不同的方法(包括DrawingVisual)后,似乎绘制具有如此多顶点的折线效率太低。
我最终实现了在每个像素有1个或更少顶点的情况下绘制折线的方法。否则,我手动渲染到WriteableBitmap对象。这令人惊讶地高效得多。
答案 1 :(得分:0)
我发现绘制频繁更新的几何图形的最快方法是创建DrawingGroup
&#34; backingStore&#34;,在OnRender()
期间输出支持存储,然后更新我需要更新数据时使用backingStore.Open()
进行备份。 (见下面的代码)
在我的测试中,这比使用WriteableBitmap
或RenderTargetBitmap
更有效。
如果你的用户界面变得反应迟钝,你怎么每50毫秒触发一次你的重绘?是否可能有一些重绘时间超过50毫秒并使用重绘消息备份消息泵?避免这种情况的一种方法是在重绘循环期间关闭重绘计时器(或使其成为一次性计时器),并且仅在结束时启用它。另一种方法是在CompositionTarget.Rendering
事件期间进行重绘,这发生在WPF重绘之前。
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}