Silverlight 5:DropShadowEffect的可怕性能问题

时间:2012-02-09 20:33:03

标签: silverlight performance effect

环境信息:

  

Windows 7 64位SP1

     

6GB内存

     

Intel(R)Core(TM)i5-2400S CPU @ 2.50GHz(4核)

     

Silverlight 5

我在Silerlight中遇到DropShadowEffect非常可怕的性能问题。

我为它创建了一个最小化的演示: http://www.peterlee.com.cn/public/ShadowEffectTestDemo/ShadowEffectTestTestPage.html

单击按钮Zoom以尝试缩放画布(带有DropShadowEffect的星形)。你会发现当Zoom = 64时,你的CPU和内存非常疯狂。

但是,如果您通过点击DropShadowEffect按钮删除Remove Effect,然后将其缩放,一切正常。

但是,如果我们使用TextBlock DropShadowEffect,一切都很好。您可以通过单击“使用TextBlock”按钮来尝试它。

我不知道Silverlight正在处理TextBlock和DropShadowEffect的自定义Star形状。

请帮帮我。感谢。

更新: 根据回复:

我也在发布模式下尝试过(现在示例演示链接是在发布模式下构建的);

我还添加了ChrisF提出的GPA加速:

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
  <param name="EnableGPUAcceleration" value="true" />
  <param name="source" value="ShadowEffectTest.xap"/>
  <param name="onError" value="onSilverlightError" />
  <param name="background" value="white" />
  <param name="minRuntimeVersion" value="5.0.61118.0" />
  <param name="autoUpgrade" value="true" />
  <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0" style="text-decoration:none">
     <img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
  </a>
</object>

这是更新的代码

MainPage.xmal:

<UserControl x:Class="ShadowEffectTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Button x:Name="btnZoom" Width="75" Height="23" Content="Zoom"
                Grid.Column="0" Grid.Row="0"
                Click="btnZoom_Click"/>
        <Button x:Name="btnRemoveEffect" Width="100" Height="23" Content="Remove Effect"
                Grid.Row="0" Grid.Column="1"
                Click="btnRemoveEffect_Click"/>

        <Button x:Name="btnUseTextBlock" Width="120" Height="23" Content="Use TextBlock"
                Grid.Row="1" Grid.Column="0"
                Click="btnUseTextBlock_Click" />

        <Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightBlue"
                Grid.Column="1" Grid.Row="1" />
    </Grid>
</UserControl>

MainPage.xaml.cs中:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Windows.Media.Effects;

namespace ShadowEffectTest
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

// These are the points for a Star shape
            List<Point> points = new List<Point>()
            {
                new Point(0, 36.3634469292486),
                new Point(-12.3606797688474, 43.2188191057189),
                new Point(-24.7213595376948, 50.0741912821893),
                new Point(-37.0820393065422, 56.9295634586596),
                new Point(-34.4313595448175, 42.9654855127096),
                new Point(-31.7806797830927, 29.0014075667595),
                new Point(-29.130000021368, 15.0373296208095),
                new Point(-39.4200000080385, 5.31010468057062),
                new Point(-49.709999994709, -4.41712025966827),
                new Point(-59.9999999813794, -14.1443451999072),
                new Point(-46.0011100186002, -15.919247828416),
                new Point(-32.002220055821, -17.694150456925),
                new Point(-18.0033300930418, -19.4690530854338),
                new Point(-12.0022200620278, -32.3361808961241),
                new Point(-6.00111003101392, -45.2033087068145),
                new Point(0, -58.0704365175048),
                new Point(6.00111003101392, -45.2033087068145),
                new Point(12.0022200620278, -32.3361808961241),
                new Point(18.0033300930418, -19.4690530854338),
                new Point(32.002220055821, -17.694150456925),
                new Point(46.0011100186002, -15.919247828416),
                new Point(59.9999999813794, -14.1443451999072),
                new Point(49.709999994709, -4.41712025966827),
                new Point(39.4200000080385, 5.31010468057062),
                new Point(29.130000021368, 15.0373296208095),
                new Point(31.7806797830927, 29.0014075667595),
                new Point(34.4313595448175, 42.9654855127096),
                new Point(37.0820393065422, 56.9295634586596),
                new Point(24.7213595376948, 50.0741912821893),
                new Point(12.3606797688474, 43.2188191057189),
                new Point(0, 36.3634469292486)
            };

            uie = RenderBezier(points);

// This the evil for the performance issues (crazy memory and CPU)
            uie.Effect = ShadowEffect;
            uie.CacheMode = new BitmapCache();

            uie.SetValue(Canvas.LeftProperty, 25.0);
            uie.SetValue(Canvas.TopProperty, 25.0);

            mainCanvas.Children.Add(uie);
        }
        private UIElement uie = null;

        public Path RenderBezier(List<Point> ctrlPoints, List<List<Point>> innersCtrlPoints = null)
        {
            // Step 0: Merge ctrlPoints lists
            List<List<Point>> ctrlPointsLists;
            if (innersCtrlPoints == null)
            {
                ctrlPointsLists = new List<List<Point>>(1);
                ctrlPointsLists.Add(ctrlPoints);
            }
            else
            {
                ctrlPointsLists = new List<List<Point>>(1 + innersCtrlPoints.Count);
                ctrlPointsLists.Add(ctrlPoints);
                foreach (List<Point> list in innersCtrlPoints)
                    ctrlPointsLists.Add(list);
            }

            PathGeometry pg = new PathGeometry();
            foreach (List<Point> list in ctrlPointsLists)
            {
                // Step 0: check (Debug.Assert)
                Debug.Assert(list.Count % 3 == 1,
                    "public Path RenderBezier(IList<Point> ctrlPoints): number of control points is not 3n+1.");

                int n = (list.Count - 1) / 3; // Number of BezierSegments
                Debug.Assert(n > 0,
                    "public Path RenderBezier(IList<Point> ctrlPoints): at least one Bezier segment required.");

                // Step 1: Add BezierSegments to PathFigure
                PathFigure pf = new PathFigure();
                pf.StartPoint = list[0];
                for (int i = 0; i < n; ++i)
                    pf.Segments.Add(GetBezierSegment(
                                                        list[3 * i + 1],
                                                        list[3 * i + 2],
                                                        list[3 * i + 3]
                                                    ));

                // Step 2: Add PathFigures to PathGeometry
                pg.Figures.Add(pf);
            }

            // Step 3: Add PathGemotry to GeometryGroup
            GeometryGroup gg = new GeometryGroup();
            gg.Children.Add(pg);

            // Step 4: Set GeometryGroup as Path.Data
            Path path = new Path();
            path.Data = gg;

            // Step 5: Set some Path properties
//            if (ShowOutline)
            {
                path.Stroke = new SolidColorBrush(Colors.Black);
                path.StrokeThickness = 1.0;
                path.StrokeEndLineCap = PenLineCap.Round;
                path.StrokeLineJoin = PenLineJoin.Round;
                path.StrokeStartLineCap = PenLineCap.Round;
            }

            // Finally, return it
            return path;
        }

// This the evil for the performance issues (crazy memory and CPU)
        private static DropShadowEffect ShadowEffect
        {
            get
            {
                return new DropShadowEffect()
                       {
                           Color = Colors.Blue,
                           BlurRadius = 5,
                           Direction = 0,
                           ShadowDepth = 0
                       };
            }
        }

        private static BezierSegment GetBezierSegment(Point p1, Point p2, Point p3)
        {
            BezierSegment bs = new BezierSegment();
            bs.Point1 = p1;
            bs.Point2 = p2;
            bs.Point3 = p3;
            return bs;
        }

        public static readonly double[] ZoomingSteps = new double[]
        {
            1.0,
            1.5,
            2.0,
            3.0,
            4.0,
            6.0,
            8.0,
            12.0,
            16.0,
            24.0,
            32.0,
            48.0,
            64.0,
            128.0
        };
        private int index = 0;
        private void btnZoom_Click(object sender, RoutedEventArgs e)
        {
            if (index >= ZoomingSteps.Length - 1)
                return;

            ScaleTransform st = new ScaleTransform();
            st.ScaleX = st.ScaleY = ZoomingSteps[index++];

            btnZoom.Content = ZoomingSteps[index].ToString();

            mainCanvas.RenderTransform = st;
        }

        private void btnRemoveEffect_Click(object sender, RoutedEventArgs e)
        {
            index = 0;
            btnZoom.Content = "Zoom";
            uie.Effect = null;
            mainCanvas.RenderTransform = new ScaleTransform();
        }


// If I use TextBlock as the child UIElement, then everything is okay
//            path = new TextBlock() { Text = "Text Block" };
        private void btnUseTextBlock_Click(object sender, RoutedEventArgs e)
        {
            mainCanvas.Children.Remove(uie);

            index = 0;
            btnZoom.Content = "Zoom";
            uie = new TextBlock() { Text = "Text Block" };

            mainCanvas.Children.Add(uie);

            uie.Effect = ShadowEffect;
            uie.CacheMode = new BitmapCache();
            mainCanvas.RenderTransform = new ScaleTransform();
        }
    }
}

4 个答案:

答案 0 :(得分:0)

我已经尝试针对您的应用程序运行WinDbg并转储应用程序。这是Result.现在别担心,我也不理解其中的一半。但是我走得更远了,似乎有大量的数组/列表,其中至少80%为空。

您可能想亲自查看WinDbg。这是一个小Tutorial

如果可以的话,我会在早上试着看看它,我希望我能帮助你朝着正确的方向前进

答案 1 :(得分:0)

这是我的诀窍:永远不要使用DropShadowEffect。创建要为其创建投影的任何内容的克隆,将其放在主要内容后面,并在其上使用BlurEffect。性能更好,质量更好。我不明白为什么。

答案 2 :(得分:0)

我认为问题在于:

uie.CacheMode = new BitmapCache();

通常,这会告诉silverlight尝试将位图缓存在内存中,以便计算机不需要在应用程序中重新呈现元素。

在此处查看有关silverlight性能的建议提示:

http://msdn.microsoft.com/en-us/library/cc189071(v=vs.95).aspx

答案 3 :(得分:0)

问题与Silverlight的渲染管道如何工作有关。 DropShadowEffect只是一个像素着色器,像素着色器通过创建它们将应用的图像的缓冲副本在Silverlight中工作,然后允许您使用缓冲副本的值更改实际图像的像素属性。此缓冲副本是整个图像,不受剪切影响。由于缩放会创建一个非常大的预剪裁图像,因此像素着色器必须制作一个非常大的预剪裁图像的物理缓冲副本......这就是缩放时性能如此糟糕的原因。

我找到的唯一解决方案是在缩放(或平移缩放的图像)时禁用像素着色器效果,并在放大图像时将其添加回来(或平移完成)。这样,您不会因为缩放或平移的每一帧而应用像素着色器的性能损失。