【WPF学习】第五十二章 动画性能
通常,为用户界面应用动画只不过是创建并配置正确的动画和故事板对象。但在其他情况下,特别是同时发生多个动画时,可能需要更加关注性能。特定的效果更可能导致这些问题——例如,那些涉及视频、大位图以及多层透明等的效果通常需要占用更多CPU开销。如果不谨慎实现这类效果,运行它们使可能造成明显抖动,或者会从其他同时运行的应用程序抢占CPU时间。
幸运的是,WPF提供了几个可提供帮助的技巧。接下来的几节将学习降低最大帧率以及缓存计算机显卡中的位图,这两种技术可以减轻CPU的负担。
一、期望的帧率
正如前面所学习的,WPF试图保持以60帧/秒的速度运动动画。这样可确保从开始到结束得到平滑流畅的动画。当然,WPF可能达不到这个目标。如果同时运行多个复杂的动画,并且CPU或显卡不能承受的话,整个帧率可能会下降(最好的情形),甚至可能会跳跃以进行补偿(最坏的情形)。
尽管很少提高帧率,但可能会选择降低帧率,这可能是因为以下两个原因之一:
- 动画使用更低的帧率看起来也很好,所以不希望浪费额外的CPU周期。
- 应用程序运行在性能较差的CPU或显卡上,并知道使用高的帧率时整个动画的渲染效果还不如使用更低的帧率的渲染效果好。
调整帧率很容易。只需要为包含动画的故事板使用Timeline.DesiredFrameRate附加属性。下面的示例将帧率减半:
<Storyboard Timeline.DesiredFrameRate="30">
下图显示了一个简单的测试程序,该程序为一个小球应用动画,使其在Canvas控件上沿一条曲线运动。
这个应用程序开始在Canvas上绘制Ellipse对象。Canvas.ClipToBounds属性被设置为true,所以圆的边缘不会超出Canvas控件的边缘而进入窗口的其他部分。
<Canvas ClipToBounds="True"> <Ellipse Name="ellipse" Fill="Red" Width="10" Height="10"></Ellipse> </Canvas>
为在Canvas控件上移动圆,需要同时进行两个动画——一个动画用于更新Canva.Left属性(从左向右移动圆),另一个动画用于改变Canvas.Top属性(使圆上升,然后下降)。Canvas.Top动画是可反转的——一旦圆达到最高点,就会下降。Canvas.Left动画不是可反转的,但持续时间是Canvas.Top动画的两倍,从而使得这两个动画可以同时移动圆。最后的技巧是为Canvas.Top动画使用DeceleartionRatio属性。这样,当圆达到最高点是上升的速度会更慢,这会创建更逼真的效果。
下面是动画的完整标记:
<Window.Resources> <BeginStoryboard x:Key="beginStoryboard"> <Storyboard Timeline.DesiredFrameRate="{Binding ElementName=txtFrameRate,Path=Text}"> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Left)" From="0" To="300" Duration="0:0:5"> </DoubleAnimation> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Top)" From="300" To="0" AutoReverse="True" Duration="0:0:2.5" DecelerationRatio="1"> </DoubleAnimation> </Storyboard> </BeginStoryboard> </Window.Resources>
这个示例的真正目的是尝试不同的帧率。为查看某个特定帧率的效果,只需要在文本框中输入合适的数值,然后单击Repeat按钮即可。然后动画就会使用新的帧率(通过数据绑定表达式获取新的帧率)触发,从而可以观察动画的效果。在更低的帧率下,椭圆不会均匀移动——而会在Cavans控件中的跳跃。
也可使用代码调整Timeline.DesiredFrame属性。例如,可能希望读取静态属性RenderCapability.Tier以确定显卡支持的渲染级别。
二、位图缓存
位图缓存通知WPF获取内容的当前位图图像,并将其复制到显卡的内存中。这时,显卡可以控制位图的操作和显示的刷新。这个处理过程比让WPF完成所有工作要快很多,并且和显卡不断通信。
如果运用得当,位图缓存可以改善应用程序的绘图性能。但如果运用不当,就会浪费显存并且实际上会降低性能。所以,在使用位图缓存之前,需要确保真正合适。下面列出一些指导原则:
- 如果正在绘制的内容需要频繁地重新绘制,使用位图缓存可能是合理的。因为每次后续的重新绘制将更快。一个例子是当其他一些具有动画的对象浮动在形状表面上时,使用BitmapCacheBrush画刷绘制形状的表面。尽管形状没有变化,但是形状的不同部分被遮挡住或显露出来,从而需要重新绘制。
- 如果元素的内容经常变化,使用位图缓存可能不合理。因为可视化内容每次改变时,WPF需要重新渲染位图将其发送到显卡缓存,而这需要耗费时间。该规则有些晦涩,因为某些改变不会导致缓存无效。安全操作的例子包括使用变换旋转以及重新缩放元素、剪裁元素、改变元素的透明度以及应用效果。另一方面,改变元素的内容、布局以及各式将强制重新渲染位图。
- 尽量少缓存内容。位图越大,WPF存储缓存副本所需的时间越长,需要的显存越多。一旦耗尽显存,WPF将被迫使用更慢的软件渲染。
为更好地理解位图缓存,使用一个简单示例是有帮助的,下图例举一个示例,一个动画推动一个简单的图像——正方形——在Canvas面板上移动,Canvas面板包含一条具有复杂集合图形的路径。但正方形在Canvas面板表面上移动时,强制WPF重新计算路径并填充丢失的部分。这会带来极大的CPU负担,并且动画甚至可能开始变得断断续续。
可采用几种方法解决该问题。一种选择是使用一幅位图替换背景,WPF能够更高效地管理位图。更灵活的选择是使用位图缓存,这种方法可继续将存活的、可交互的元素作为背景。
为启用位图缓存功能,将相应元素的CacheMode属性设置为BitmapCache。每个元素都提供了CacheMode属性,这意味着可以精确选择为哪个元素使用这一特征。
<Path CacheMode="BitmapCache" ...></Path>
通过这个简单修改,可立即看到区别。首先,窗口显示的事件要稍长一些。但动画的运行将更平滑,并且CPU的负担将显著降低。可通过Windows任务管理器进行检查——经常可以看到CPU的负担从接近100%减少到20%一下。
通常,当启用位图缓存时,WPF采用元素当前尺寸的快照并将其位图复制到显卡中。如果之后使用ScaleTransform放大元素,这会变成一个问题。在这种情况下,将放大缓存的位图,而不是实际的元素,当放大元素时这会导致模糊放大以及色块。
例如,设想一个修订过的示例。在这个示例中,第二个同步动画扩展Path使其为原始尺寸的10倍,然后缩回原始尺寸。为确保具有良好的显示质量,可使用5倍于Path原始尺寸的尺寸缓存其位图:
<Path ...> <Path.CacheMode> <BitmapCache RenderAtScale="5"></BitmapCache> </Path.CacheMode> </Path>
这样可解决像素化问题。虽然缓存的位图仍比Path的最大动画尺寸(最大尺寸达10倍于其原始尺寸)小,但显卡能使位图的尺寸加倍,从5倍到10倍,而不会有任何明显的缩放问题。更重要的是,这可使应用避免过多地使用显存。