[UWP]使用AlphaMaskEffect提升故障艺术动画的性能(顺便介绍怎么使用性能探测器分析UWP程序)
dino.c 人气:0
前几天发布了[抄抄《CSS 故障艺术》的动画](https://www.cnblogs.comhttps://img.qb5200.com/download-x/dino623/p/UWP_Glitch_Art.html)这篇文章,在这篇文章里介绍了如何使用Win2D绘制文字然后配合BlendEffect制作故障艺术的动画。本来打算就这样收手不玩这个动画了,但后来又发现性能不符合理想。明明只是做做Resize动画和用BlendEffect混合,为什么性能会这么差呢?
## 1. 分析原因
其实不用分析都知道哪里出问题了,毕竟这个懒是自己偷的,不过这里顺便介绍介绍Visual Studio的性能分析。Visual Studio不停更新它的性能探测器,最近几年我还挺喜欢的的“应用程序时间线”功能,对桌面应用来说这个功能很好用,可以直观地看到帧率、CPU使用、布局消耗、呈现消耗等信息。
要开始性能分析,首先在顶部菜单选择“调试”->“性能探测器”:
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121654768-1057702165.png)
在打开的性能探测器配置页面,选中“CPU使用率”和“应用程序时间线”两个工具后点击“开始”按钮:
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121702091-693346189.png)
之后Visual Studio就会启动性能会话并运行程序,切换到打开的应用程序里,一顿操作后关闭程序,稍等一下就可以看到分析报告。
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121708855-1321955967.png)
为了凸显性能问题,我复制粘贴了好几个个故障艺术的动画,可以看到后半段的FPS下降了,且“应用程序代码”占了很大的比例。切换到"CPU使用率"选项卡,能看到具体的CPU消耗都在`DrawSurfaceCore`这个函数附近
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121720289-1069673261.png)
双击`DrawSurfaceCore`这行进去具体代码,这里颜色越红代表CPU占用率越高,并且会在源码左侧显示具体的CPU占用率,很明显这里的代码很糟糕,那么罪魁祸首就是这堆代码了。
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121726459-1745104126.png)
## 2. 使用AlphaMaskEffect优化性能
上面的这段代码是使用Win2D绘制文字和使用`GaussianBlurEffect`制作阴影。本来这代码性能应该没问题(当然,在这个动画里有优化空间,例如因为我在这里总是使用`BlurAmount = 0`的阴影所以根本不需要`GaussianBlurEffect`也不需要`DrawImage`),但是我使用了Storyboard控制文字的高度,然后每次高度改变都重新调用这个函数绘制文字。从结果上来说我的代码在不停画图,所以小小的动画造成了巨大的性能消耗。
现在我要做什么才可以改善这种状况?当然上面这段代码有很多优化的空间,但最根本要做的是应该少调用这段代码,少重新绘图。一个很复杂的情况是,我需要使用两个这段代码绘制出来的`CompositionSurfaceBrush`作为BlendEffect的输入,而`CompositionSurfaceBrush`本质上是一张位图,而作为Brush又没法修改它的尺寸。`CompositionSurfaceBrush`关联了一个`CompositionDrawingSurface`,后者虽然有`Resize`函数,但使用这个函数会令图片在动画过程中移位,明明单独使用`Resize`效果不错,但用在动画里就总是错,我也没心思去纠结它的原因。
其实要改变Brush的高度,一种很实在的方法是使用遮罩。CompositionApi提供了[CompositionMaskBrush](https:/https://img.qb5200.com/download-x/docs.microsoft.com/zh-cn/uwp/api/Windows.UI.Composition.CompositionMaskBrush),使用它可以实现OpacityMask的效果,复习一下它的源码:
[paint-with-a-compositionbrush-with-opacity-mask-applied](https:/https://img.qb5200.com/download-x/docs.microsoft.com/zh-cn/windows/uwp/composition/composition-brushes#paint-with-a-compositionbrush-with-opacity-mask-applied)
``` CS
Compositor _compositor;
SpriteVisual _maskVisual;
CompositionMaskBrush _maskBrush;
_compositor = Window.Current.Compositor;
_maskBrush = _compositor.CreateMaskBrush();
CompositionLinearGradientBrush _sourceGradient = _compositor.CreateLinearGradientBrush();
_sourceGradient.ColorStops.Add(_compositor.CreateColorGradientStop(0,Colors.Red));
_sourceGradient.ColorStops.Add(_compositor.CreateColorGradientStop(1,Colors.Yellow));
_maskBrush.Source = _sourceGradient;
LoadedImageSurface loadedSurface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///Assets/circle.png"), new Size(156.0, 156.0));
_maskBrush.Mask = _compositor.CreateSurfaceBrush(loadedSurface);
_maskVisual = _compositor.CreateSpriteVisual();
_maskVisual.Brush = _maskBrush;
_maskVisual.Size = new Vector2(156, 156);
```
使用`CompositionMaskBrush`之前首先要有一张作为Mask的图片,用Paint.Net两三下就做好了,比奥特曼打到怪兽还快。
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121735324-1619353119.png)
接下来只要用显示文字的`CompositionSurfaceBrush`作为`CompositionMaskBrush`的Source,用上面这张图片制作的`CompositionSurfaceBrush`作为Mask,再对Mask做Scale的动画,高度改变的动画就…………
就报错了。
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200328121831699-806084236.png)
好吧,我想起来了文档里就说明了`CompositionMaskBrush`不能玩BlendEffect。
不过幸运的是Win2D本来就提供了[AlphaMaskEffect](https://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_AlphaMaskEffect.htm)这个类,它的作用几乎和`CompositionMaskBrush`一样,我之前都没想到会有使用它的一天。使用它的代码大同小异,两三下就写完了:
``` CS
private (CompositionBrush, CompositionSurfaceBrush) CreateMaskedBrush(CompositionBrush source)
{
var compositor = Window.Current.Compositor;
var effect = new AlphaMaskEffect()
{
Source = new CompositionEffectSourceParameter("Source"),
AlphaMask = new CompositionEffectSourceParameter("Mask"),
};
var opacityMaskSurface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///Assets/Images/mask.Png"));
var opacityBrush = Compositor.CreateSurfaceBrush(opacityMaskSurface);
opacityBrush.Stretch = CompositionStretch.UniformToFill;
var effectFactory = compositor.CreateEffectFactory(effect);
var compositionBrush = effectFactory.CreateBrush();
compositionBrush.SetSourceParameter("Source", source);
compositionBrush.SetSourceParameter("Mask", opacityBrush);
return (compositionBrush, opacityBrush);
}
```
## 3. 结果
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200331085317380-802450971.gif)
左边是旧的代码(每次改变高度重新绘图),右边是新的代码(对作为Mask的`CompositionSurfaceBrush`进行Scale动画),可以看到……嗯,好像新动画是刘畅了些。
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200331085327420-599245594.gif)
![](https://img2020.cnblogs.com/blog/38937/202003/38937-20200331085544534-855361788.png)
看起来再玩大些都还撑得住,GPU占用率还算满意,CPU占用率也不高。其实还有不少优化空间,但我还是完全想不到这个动画实际应用场景(恕我想象力贫乏),所以就到吃为止吧。
## 4. 参考
[CompositionMaskBrush Class (Windows.UI.Composition) - Windows UWP applications Microsoft Docs](https:/https://img.qb5200.com/download-x/docs.microsoft.com/zh-cn/uwp/api/Windows.UI.Composition.CompositionMaskBrush)
[AlphaMaskEffect Class](https://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_AlphaMaskEffect.htm)
[合成画笔 - UWP applications Microsoft Docs](https:/https://img.qb5200.com/download-x/docs.microsoft.com/zh-cn/windows/uwp/composition/composition-brushes)
加载全部内容