I'm creating a large number of texts in WPF using DrawText
and then adding them to a single Canvas
.
我正在使用DrawText在WPF中创建大量文本,然后将它们添加到一个画布中。
I need to redraw the screen in each MouseWheel
event and I realized that the performance is a bit slow, so I measured the time the objects are created and it was less than 1 milliseconds!
我需要在每个鼠标滚轮事件中重新绘制屏幕,我意识到性能有点慢,所以我测量了创建对象的时间,它小于1毫秒!
So what could be the problem? A long time ago I guess I read somewhere that it actually is the Rendering
that takes the time, not creating and adding the visuals.
那么问题是什么呢?很久以前,我在什么地方读到过,渲染需要时间,而不是创建和添加视觉效果。
Here is the code I'm using to create the text objects, I've only included the essential parts:
这是我用来创建文本对象的代码,我只包含了最基本的部分:
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;
}
}
}
And this code is run each time the MouseWheel
event is fired:
每次触发鼠标滚轮事件时,都会运行此代码:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
What could be the culprit?
什么可能是罪魁祸首?
I'm also having trouble while panning:
我在淘金时也遇到了麻烦:
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;
}
3 个解决方案
#1
15
I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.
我目前正在处理的可能是同样的问题,我发现了一些非常出乎意料的事情。我将渲染到一个WriteableBitmap,允许用户滚动(缩放)和平移来改变渲染的内容。对于缩放和平移来说,动作看起来都很不稳定,所以我自然觉得渲染花的时间太长了。在使用了一些工具之后,我验证了我的渲染速度是30-60 fps。无论用户如何缩放或平移,渲染时间都不会增加,所以异常一定来自其他地方。
I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).
相反,我查看了OnMouseMove事件处理程序。WriteableBitmap每秒更新30-60次,而MouseMove事件每秒更新1-2次。如果我减小WriteableBitmap的大小,则MouseMove事件会更频繁地触发,pan操作会更平滑。所以这种不稳定实际上是由于MouseMove事件是不稳定的,而不是渲染的结果(例如,WriteableBitmap渲染的是7-10帧看起来相同的帧,一个MouseMove事件触发,然后WriteableBitmap渲染的是新窗格图像的7-10帧,等等)。
I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.
我尝试通过每次使用Mouse.GetPosition(这个)来轮询鼠标位置来跟踪pan操作。但结果是一样的,因为返回的鼠标位置对于7-10帧来说是相同的,然后才会更改为一个新值。
I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:
然后,我尝试使用PInvoke服务GetCursorPos来轮询鼠标位置,如下所示
[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;
}
}
and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.
这个方法很有效。GetCursorPos每次调用时都返回一个新位置(当鼠标移动时),因此在用户执行平移操作时,每个帧的渲染位置略有不同。同样的烦躁似乎也影响着鼠标滚轮事件,我不知道该如何处理它。
So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.
因此,尽管上面关于有效维护可视树的所有建议都是很好的实践,但我怀疑您的性能问题可能是由于某些因素干扰了鼠标事件频率。在我的示例中,由于某些原因,呈现导致鼠标事件更新和启动速度比通常慢得多。如果我找到一个真正的解决方案,而不是这个局部的解决方案,我将更新它。
Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:
编辑:好的,我再深入一点,我想我现在明白了。我将用更详细的代码示例进行解释:
I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.
通过注册以处理CompositionTarget,我可以在每帧的基础上呈现位图。呈现事件,如本文中所述。基本上,它意味着每次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);
}
}
The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.
问题是,由于渲染需要更多的时间,所以MouseMove事件(以及所有的鼠标事件)被调用的频率要低得多。当渲染代码花费15ms时,每隔几毫秒调用一次MouseMove事件,当渲染代码花费30ms时,每隔几百毫秒调用一次MouseMove事件。我的理论是,渲染发生在WPF鼠标系统更新其值并触发鼠标事件的同一个线程上。这个线程上的WPF循环必须有一些条件逻辑,如果渲染在一帧中花费的时间太长,它就会跳过鼠标更新。当我的呈现代码在每一帧上花费的时间“太长”时,问题就出现了。然后,界面不会因为渲染每帧多花费15毫秒而显得慢一点,而是因为额外的15毫秒渲染时间会在鼠标更新之间带来数百毫秒的延迟。
The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.
我之前提到的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;
}
}
}
This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.
这种方法没有修复鼠标事件的其余部分(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);
}
}
Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.
将相同的知识转换为您自己的特定情况:对于导致性能问题的复杂几何图形,我将尝试某种类型的缓存。例如,如果几何图形本身从未更改,或者它们不经常更改,那么尝试将它们呈现到RenderTargetBitmap,然后将RenderTargetBitmap添加到可视化树中,而不是添加几何图形本身。这样,当WPF执行渲染路径时,它所需要做的就是对这些位图进行blit,而不是从原始几何数据中重构像素数据。
#2
4
The likely culprit is the fact that you are clearing out and rebuilding your visual tree on each wheel event. According to your own post, that tree includes a "large number" of text elements. For each event that comes in, each of those text elements must be recreated, reformatted, measured, and eventually rendered. That is not the way to accomplish simple text scaling.
可能的罪魁祸首是你正在清理和重建你在每个*事件上的视觉树。根据您自己的帖子,该树包含“大量”文本元素。对于出现的每个事件,必须重新创建、重新格式化、测量并最终呈现这些文本元素。这不是实现简单文本缩放的方法。
Rather than setting a ScaleTransform
on each FormattedText
element, set one on the element containing the text. Depending on your needs, you can set a RenderTransform
or LayoutTransform
. Then, when you receive wheel events, adjust the Scale
property accordingly. Don't rebuild the text on each event.
与其在每个FormattedText元素上设置ScaleTransform,不如在包含文本的元素上设置一个。根据需要,可以设置RenderTransform或LayoutTransform。然后,当您接收到*事件时,相应地调整Scale属性。不要在每个事件上重建文本。
I would also do what other have recommended and bind an ItemsControl
to the list of columns and generate the text that way. There is no reason you should need to do this by hand.
我还将按照其他人的建议,将ItemsControl绑定到列列表,并以这种方式生成文本。你没有必要手工做这件事。
#3
2
@Vahid: the WPF system is using [retained graphics]. What you eventually should do, is devise a system where you only send "what has changed compared to previous frame" - nothing more, nothing less, you should not be creating new objects at all. It's not about "creating objects takes zero seconds", it's about how it affects rendering and the time. It's about letting the WPF do it's job using caching.
@Vahid: WPF系统使用[保留图形]。你最终应该做的,是设计一个系统,在这个系统中,你只发送“与前一帧相比发生了变化的内容”——没有更多,也没有更少,你根本不应该创建新的对象。它不是“创建对象需要零秒”,而是它如何影响渲染和时间。它是关于让WPF使用缓存来完成它的工作。
Sending new objects to the GPU for rendering=slow. Sending only updates to the GPU which tells what objects moved=fast.
向GPU发送新对象进行渲染=慢。只向GPU发送更新,GPU告诉什么对象移动得快=快。
Also, it's possible to create Visuals in an arbitrary thread to improve the performance (Multithreaded UI: HostVisual - Dwayne Need). That all said, if your project is pretty complex in 3D wise - there's good chance that WPF won't just cut it. Using DirectX.. directly, is much, much, more performant!
此外,可以在任意线程中创建可视化,以提高性能(多线程UI: HostVisual——Dwayne需要)。也就是说,如果您的项目在3D方面相当复杂,那么WPF很有可能不会直接削减它。使用举. .直接的,是更多,更多的表现!
Some of the articles I suggest you to read & understand:
我建议您阅读和理解以下文章:
[Writing More Efficient ItemsControls - Charles Petzold] - understand the process how one achieves better drawing rate in WPF.
[编写更有效的项目控制- Charles Petzold] -理解如何在WPF中获得更好的绘制速度。
As for why your UI is lagging, Dan answer seems to be spot on. If you are trying to render more than WPF can handle, the input system will suffer.
至于为什么你的UI是滞后的,Dan的回答似乎是正确的。如果您试图呈现超出WPF所能处理的内容,输入系统将会受到影响。
#1
15
I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.
我目前正在处理的可能是同样的问题,我发现了一些非常出乎意料的事情。我将渲染到一个WriteableBitmap,允许用户滚动(缩放)和平移来改变渲染的内容。对于缩放和平移来说,动作看起来都很不稳定,所以我自然觉得渲染花的时间太长了。在使用了一些工具之后,我验证了我的渲染速度是30-60 fps。无论用户如何缩放或平移,渲染时间都不会增加,所以异常一定来自其他地方。
I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).
相反,我查看了OnMouseMove事件处理程序。WriteableBitmap每秒更新30-60次,而MouseMove事件每秒更新1-2次。如果我减小WriteableBitmap的大小,则MouseMove事件会更频繁地触发,pan操作会更平滑。所以这种不稳定实际上是由于MouseMove事件是不稳定的,而不是渲染的结果(例如,WriteableBitmap渲染的是7-10帧看起来相同的帧,一个MouseMove事件触发,然后WriteableBitmap渲染的是新窗格图像的7-10帧,等等)。
I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.
我尝试通过每次使用Mouse.GetPosition(这个)来轮询鼠标位置来跟踪pan操作。但结果是一样的,因为返回的鼠标位置对于7-10帧来说是相同的,然后才会更改为一个新值。
I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:
然后,我尝试使用PInvoke服务GetCursorPos来轮询鼠标位置,如下所示
[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;
}
}
and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.
这个方法很有效。GetCursorPos每次调用时都返回一个新位置(当鼠标移动时),因此在用户执行平移操作时,每个帧的渲染位置略有不同。同样的烦躁似乎也影响着鼠标滚轮事件,我不知道该如何处理它。
So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.
因此,尽管上面关于有效维护可视树的所有建议都是很好的实践,但我怀疑您的性能问题可能是由于某些因素干扰了鼠标事件频率。在我的示例中,由于某些原因,呈现导致鼠标事件更新和启动速度比通常慢得多。如果我找到一个真正的解决方案,而不是这个局部的解决方案,我将更新它。
Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:
编辑:好的,我再深入一点,我想我现在明白了。我将用更详细的代码示例进行解释:
I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.
通过注册以处理CompositionTarget,我可以在每帧的基础上呈现位图。呈现事件,如本文中所述。基本上,它意味着每次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);
}
}
The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.
问题是,由于渲染需要更多的时间,所以MouseMove事件(以及所有的鼠标事件)被调用的频率要低得多。当渲染代码花费15ms时,每隔几毫秒调用一次MouseMove事件,当渲染代码花费30ms时,每隔几百毫秒调用一次MouseMove事件。我的理论是,渲染发生在WPF鼠标系统更新其值并触发鼠标事件的同一个线程上。这个线程上的WPF循环必须有一些条件逻辑,如果渲染在一帧中花费的时间太长,它就会跳过鼠标更新。当我的呈现代码在每一帧上花费的时间“太长”时,问题就出现了。然后,界面不会因为渲染每帧多花费15毫秒而显得慢一点,而是因为额外的15毫秒渲染时间会在鼠标更新之间带来数百毫秒的延迟。
The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.
我之前提到的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;
}
}
}
This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.
这种方法没有修复鼠标事件的其余部分(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);
}
}
Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.
将相同的知识转换为您自己的特定情况:对于导致性能问题的复杂几何图形,我将尝试某种类型的缓存。例如,如果几何图形本身从未更改,或者它们不经常更改,那么尝试将它们呈现到RenderTargetBitmap,然后将RenderTargetBitmap添加到可视化树中,而不是添加几何图形本身。这样,当WPF执行渲染路径时,它所需要做的就是对这些位图进行blit,而不是从原始几何数据中重构像素数据。
#2
4
The likely culprit is the fact that you are clearing out and rebuilding your visual tree on each wheel event. According to your own post, that tree includes a "large number" of text elements. For each event that comes in, each of those text elements must be recreated, reformatted, measured, and eventually rendered. That is not the way to accomplish simple text scaling.
可能的罪魁祸首是你正在清理和重建你在每个*事件上的视觉树。根据您自己的帖子,该树包含“大量”文本元素。对于出现的每个事件,必须重新创建、重新格式化、测量并最终呈现这些文本元素。这不是实现简单文本缩放的方法。
Rather than setting a ScaleTransform
on each FormattedText
element, set one on the element containing the text. Depending on your needs, you can set a RenderTransform
or LayoutTransform
. Then, when you receive wheel events, adjust the Scale
property accordingly. Don't rebuild the text on each event.
与其在每个FormattedText元素上设置ScaleTransform,不如在包含文本的元素上设置一个。根据需要,可以设置RenderTransform或LayoutTransform。然后,当您接收到*事件时,相应地调整Scale属性。不要在每个事件上重建文本。
I would also do what other have recommended and bind an ItemsControl
to the list of columns and generate the text that way. There is no reason you should need to do this by hand.
我还将按照其他人的建议,将ItemsControl绑定到列列表,并以这种方式生成文本。你没有必要手工做这件事。
#3
2
@Vahid: the WPF system is using [retained graphics]. What you eventually should do, is devise a system where you only send "what has changed compared to previous frame" - nothing more, nothing less, you should not be creating new objects at all. It's not about "creating objects takes zero seconds", it's about how it affects rendering and the time. It's about letting the WPF do it's job using caching.
@Vahid: WPF系统使用[保留图形]。你最终应该做的,是设计一个系统,在这个系统中,你只发送“与前一帧相比发生了变化的内容”——没有更多,也没有更少,你根本不应该创建新的对象。它不是“创建对象需要零秒”,而是它如何影响渲染和时间。它是关于让WPF使用缓存来完成它的工作。
Sending new objects to the GPU for rendering=slow. Sending only updates to the GPU which tells what objects moved=fast.
向GPU发送新对象进行渲染=慢。只向GPU发送更新,GPU告诉什么对象移动得快=快。
Also, it's possible to create Visuals in an arbitrary thread to improve the performance (Multithreaded UI: HostVisual - Dwayne Need). That all said, if your project is pretty complex in 3D wise - there's good chance that WPF won't just cut it. Using DirectX.. directly, is much, much, more performant!
此外,可以在任意线程中创建可视化,以提高性能(多线程UI: HostVisual——Dwayne需要)。也就是说,如果您的项目在3D方面相当复杂,那么WPF很有可能不会直接削减它。使用举. .直接的,是更多,更多的表现!
Some of the articles I suggest you to read & understand:
我建议您阅读和理解以下文章:
[Writing More Efficient ItemsControls - Charles Petzold] - understand the process how one achieves better drawing rate in WPF.
[编写更有效的项目控制- Charles Petzold] -理解如何在WPF中获得更好的绘制速度。
As for why your UI is lagging, Dan answer seems to be spot on. If you are trying to render more than WPF can handle, the input system will suffer.
至于为什么你的UI是滞后的,Dan的回答似乎是正确的。如果您试图呈现超出WPF所能处理的内容,输入系统将会受到影响。