Vah*_*hid 12 c# wpf performance drawtext drawingvisual
我正在使用WPF创建大量文本DrawText,然后将它们添加到单个文本中Canvas.
我需要在每个MouseWheel事件中重绘屏幕并且我意识到性能有点慢,所以我测量了创建对象的时间并且它不到1毫秒!
那可能是什么问题呢?很久以前,我想我读到的地方实际上是Rendering花费时间,而不是创建和添加视觉效果.
这是我用来创建文本对象的代码,我只包含了基本部分:
public class ColumnIdsInPlan : UIElement
{
private readonly VisualCollection _visuals;
public ColumnIdsInPlan(BaseWorkspace space)
{
_visuals = new VisualCollection(this);
foreach (var column in Building.ModelColumnsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = "C" + Convert.ToString(column.GroupId);
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, columntextsize, columntextcolor,
null, TextFormattingMode.Display)
{
TextAlignment = TextAlignment.Left
};
// Apply Transforms
var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
dc.PushTransform(st);
// Draw Text
dc.DrawText(ft, space.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
Run Code Online (Sandbox Code Playgroud)
每次MouseWheel触发事件时都会运行此代码:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
Run Code Online (Sandbox Code Playgroud)
可能是罪魁祸首?
平移时我也遇到了麻烦:
private void Workspace_MouseMove(object sender, MouseEventArgs e)
{
MousePos.Current = e.GetPosition(Window);
if (!Window.IsMouseCaptured) return;
var tt = GetTranslateTransform(Window);
var v = Start - e.GetPosition(this);
tt.X = Origin.X - v.X;
tt.Y = Origin.Y - v.Y;
}
Run Code Online (Sandbox Code Playgroud)
Dan*_*ant 16
我目前正在处理可能出现同样问题的事情,我发现了一些非常意外的事情.我正在渲染一个WriteableBitmap并允许用户滚动(缩放)和平移以更改渲染的内容.对于缩放和平移,这个动作看起来都很不稳定,所以我自然认为渲染时间太长了.经过一些仪器测试后,我确认我的渲染速度为30-60 fps.无论用户如何缩放或平移,渲染时间都不会增加,因此波动性必须来自其他地方.
我查看了OnMouseMove事件处理程序.当WriteableBitmap每秒更新30-60次时,MouseMove事件每秒仅触发1-2次.如果我减小WriteableBitmap的大小,MouseMove事件会更频繁地触发,并且平移操作显得更平滑.因此,紊乱实际上是由于MouseMove事件不稳定而不是渲染(例如,WriteableBitmap渲染7-10帧看起来相同,MouseMove事件触发,然后WriteableBitmap渲染7-10帧的新渲染图像等).
我尝试通过使用Mouse.GetPosition(this)每次WriteableBitmap更新时轮询鼠标位置来跟踪平移操作.然而,这具有相同的结果,因为在更改为新值之前返回的鼠标位置对于7-10帧是相同的.
然后我尝试使用PInvoke服务GetCursorPos轮询鼠标位置,就像在这个SO答案中一样:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
Run Code Online (Sandbox Code Playgroud)
这实际上就是诀窍.GetCursorPos每次调用时都会返回一个新位置(当鼠标移动时),因此每个帧在用户平移时呈现的位置略有不同.同样的不稳定似乎正在影响MouseWheel事件,我不知道如何解决这个问题.
因此,尽管有关有效维护视觉树的所有上述建议都是很好的做法,但我怀疑您的性能问题可能是由于某些因素干扰鼠标事件频率造成的.在我的情况下,似乎由于某种原因,渲染导致鼠标事件更新并且比平常更慢.如果我找到一个真正的解决方案而不是这个部分解决方案,我会更新这个.
编辑:好的,我再挖一点,我想我现在明白了发生了什么.我将用更详细的代码示例解释:
我通过注册处理CompositionTarget.Rendering事件来逐帧渲染我的位图,如本MSDN文章中所述.基本上,这意味着每次呈现UI时我的代码都会被调用,因此我可以更新我的位图.这基本上等同于你正在进行的渲染,只是你的渲染代码在幕后被调用,这取决于你如何设置你的视觉元素,我的渲染代码是我能看到的.我重写OnMouseMove事件以根据鼠标的位置更新一些变量.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
base.OnMouseMove(e);
}
}
Run Code Online (Sandbox Code Playgroud)
问题在于,由于渲染需要更多时间,因此MouseMove事件(以及所有鼠标事件)的调用次数要少得多.当渲染代码需要15ms时,每隔几毫秒就会调用一次MouseMove事件.当渲染代码花费30ms时,每隔几百毫秒就会调用一次MouseMove事件.我发生这种情况的理论是,渲染发生在WPF鼠标系统更新其值并触发鼠标事件的同一线程上.此线程上的WPF循环必须具有一些条件逻辑,如果在一帧期间渲染过长,则会跳过执行鼠标更新.当我的渲染代码在每一帧上"太长"时会出现问题.然后,由于渲染每帧需要15个额外的ms,而不是接口看起来有点慢,因此接口断断续续,因为额外的15ms渲染时间在鼠标更新之间引入了数百毫秒的延迟.
我之前提到的PInvoke解决方法基本上绕过了WPF鼠标输入系统.每次渲染发生时,它都直接进入源,因此使WPF鼠标输入系统挨饿不再阻止我的位图正确更新.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
POINT screenSpacePoint;
GetCursorPos(out screenSpacePoint);
// note that screenSpacePoint is in screen-space pixel coordinates,
// not the same WPF Units you get from the MouseMove event.
// You may want to convert to WPF units when using GetCursorPos.
_mousePos = new System.Windows.Point(screenSpacePoint.X,
screenSpacePoint.Y);
// Update my WriteableBitmap here using the _mousePos variable
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法并没有修复我的鼠标事件的其余部分(MouseDown,MouseWheel等),但是我并不热衷于对我的所有鼠标输入采用这种PInvoke方法,所以我决定最好不要让WPF挨饿鼠标输入系统.我最终做的只是在真正需要更新时更新WriteableBitmap.只有在某些鼠标输入影响它时才需要更新它.因此结果是我接收一帧鼠标输入,在下一帧更新位图但在同一帧上没有接收到更多鼠标输入,因为更新需要几毫秒太长时间,然后下一帧我会收到更多鼠标输入,因为位图不需要再次更新.随着渲染时间的增加,这会产生更加线性(和合理)的性能下降,因为可变长度帧的时间只是平均值.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
private bool _bitmapNeedsUpdate;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (!_bitmapNeedsUpdate) return;
_bitmapNeedsUpdate = false;
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
_bitmapNeedsUpdate = true;
base.OnMouseMove(e);
}
}
Run Code Online (Sandbox Code Playgroud)
将相同的知识转换为您自己的特定情况:对于导致性能问题的复杂几何,我会尝试某种类型的缓存.例如,如果几何图形本身永远不会更改或者它们不经常更改,请尝试将它们渲染为RenderTargetBitmap,然后将RenderTargetBitmap添加到可视树中,而不是添加几何图形本身.这样,当WPF执行它的渲染路径时,它需要做的就是对这些位图进行blit,而不是从原始几何数据重建像素数据.
@Vahid:WPF系统正在使用[保留图形]。你最终应该做的是设计一个系统,你只发送“与前一帧相比发生了什么变化”——仅此而已,你根本不应该创建新的对象。这不是关于“创建对象需要零秒”,而是关于它如何影响渲染和时间。它是让 WPF 使用缓存来完成它的工作。
将新对象发送到 GPU 进行渲染= Slow。仅向 GPU 发送更新,告知哪些对象移动=快速。
此外,还可以在任意线程中创建视觉效果以提高性能(多线程 UI:HostVisual - Dwayne Need)。话虽如此,如果您的项目在 3D 方面相当复杂 - WPF 很可能不会简单地削减它。直接使用 DirectX.. 性能要高得多!
我建议您阅读和理解的一些文章:
[编写更高效的项目控件 - Charles Petzold] - 了解如何在 WPF 中实现更好的绘图速率的过程。
至于为什么你的用户界面滞后,丹的回答似乎是正确的。如果您尝试渲染的内容超出了 WPF 的处理能力,输入系统将会受到影响。