快速的2D图形WPF(Fast 2D graphics in WPF)

2019-08-31 14:05发布

我需要绘制大量在WPF 2D元素,如线和多边形的。 他们的位置也需要不断更新。

我已经看过很多,这里的答案使用DrawingVisual或重写的OnRender功能,主要建议的。 为了测试这些方法我已经实现了一个简单的粒子系统渲染10000个椭圆和我发现,绘图性能使用这两种方法仍然是真正的可怕。 在我的电脑我不能让远高于5-10帧每秒。 这是完全不能接受的,当你认为我容易顺利使用其他技术绘制1/2万的粒子,

所以我的问题是,我正在运行对技术限制这里WPF的还是我失去了一些东西? 有没有别的东西,我可以使用吗? 任何建议表示欢迎。

下面的代码我试过

MainWindow.xaml的内容:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500" Loaded="Window_Loaded">
    <Grid Name="xamlGrid">

    </Grid>
</Window>

MainWindow.xaml.cs的内容:

using System.Windows.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        EllipseBounce[]     _particles;
        DispatcherTimer     _timer = new DispatcherTimer();

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            //particles with Ellipse Geometry
            _particles = new EllipseBounce[10000];

            //define area particles can bounce around in
            Rect stage = new Rect(0, 0, 500, 500);

            //seed particles with random velocity and position
            Random rand = new Random();

            //populate
            for (int i = 0; i < _particles.Length; i++)
            {
               Point pos = new Point((float)(rand.NextDouble() * stage.Width + stage.X), (float)(rand.NextDouble() * stage.Height + stage.Y));
               Point vel = new Point((float)(rand.NextDouble() * 5 - 2.5), (float)(rand.NextDouble() * 5 - 2.5));
                _particles[i] = new EllipseBounce(stage, pos, vel, 2);
            }

            //add to particle system - this will draw particles via onrender method
            ParticleSystem ps = new ParticleSystem(_particles);


            //at this element to the grid (assumes we have a Grid in xaml named 'xmalGrid'
            xamlGrid.Children.Add(ps);

            //set up and update function for the particle position
            _timer.Tick += _timer_Tick;
            _timer.Interval = new TimeSpan(0, 0, 0, 0, 1000 / 60); //update at 60 fps
            _timer.Start();

        }

        void _timer_Tick(object sender, EventArgs e)
        {
            for (int i = 0; i < _particles.Length; i++)
            {
                _particles[i].Update();
            }
        }
    }

    /// <summary>
    /// Framework elements that draws particles
    /// </summary>
    public class ParticleSystem : FrameworkElement
    {
        private DrawingGroup _drawingGroup;

        public ParticleSystem(EllipseBounce[] particles)
        {
            _drawingGroup = new DrawingGroup();

            for (int i = 0; i < particles.Length; i++)
            {
                EllipseGeometry eg = particles[i].EllipseGeometry;

                Brush col = Brushes.Black;
                col.Freeze();

                GeometryDrawing gd = new GeometryDrawing(col, null, eg);

                _drawingGroup.Children.Add(gd);
            }

        }


        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            drawingContext.DrawDrawing(_drawingGroup);
        }
    }

    /// <summary>
    /// simple class that implements 2d particle movements that bounce from walls
    /// </summary>
    public class SimpleBounce2D
    {
        protected Point     _position;
        protected Point     _velocity;
        protected Rect     _stage;

        public SimpleBounce2D(Rect stage, Point pos,Point vel)
        {
            _stage = stage;

            _position = pos;
            _velocity = vel;
        }

        public double X
        {
            get
            {
                return _position.X;
            }
        }


        public double Y
        {
            get
            {
                return _position.Y;
            }
        }

        public virtual void Update()
        {
            UpdatePosition();
            BoundaryCheck();
        }

        private void UpdatePosition()
        {
            _position.X += _velocity.X;
            _position.Y += _velocity.Y;
        }

        private void BoundaryCheck()
        {
            if (_position.X > _stage.Width + _stage.X)
            {
                _velocity.X = -_velocity.X;
                _position.X = _stage.Width + _stage.X;
            }

            if (_position.X < _stage.X)
            {
                _velocity.X = -_velocity.X;
                _position.X = _stage.X;
            }

            if (_position.Y > _stage.Height + _stage.Y)
            {
                _velocity.Y = -_velocity.Y;
                _position.Y = _stage.Height + _stage.Y;
            }

            if (_position.Y < _stage.Y)
            {
                _velocity.Y = -_velocity.Y;
                _position.Y = _stage.Y;
            }
        }
    }


    /// <summary>
    /// extend simplebounce2d to add ellipse geometry and update position in the WPF construct
    /// </summary>
    public class EllipseBounce : SimpleBounce2D
    {
        protected EllipseGeometry _ellipse;

        public EllipseBounce(Rect stage,Point pos, Point vel, float radius)
            : base(stage, pos, vel)
        {
            _ellipse = new EllipseGeometry(pos, radius, radius);
        }

        public EllipseGeometry EllipseGeometry
        {
            get
            {
                return _ellipse;
            }
        }

        public override void Update()
        {
            base.Update();
            _ellipse.Center = _position;
        }
    }
}

Answer 1:

我相信提供的示例代码是非常尽善尽美,并展示该框架的限制。 在我的测试我异形的15-25ms的平均成本是由于渲染开销。 从本质上说,我们在这里讲关于中心(dependency-)属性,这是相当昂贵的只是修改。 我相信它是昂贵的,因为它更改传播到直接MIL-芯。

一个重要的说明是,间接成本是成正比的对象,其位置在模拟改变量。 本身渲染对象的大量是不是一个问题,当大多数对象是时间一致,即不改变位置。

造成这种情况的最好的替代方法是诉诸D3DImage ,这是为Windows Presentation Foundation提出与DirectX渲染信息的元素。 一般说这种做法应该是有效的,性能明智的。



Answer 2:

你可以尝试一个WriteableBitmap的,并使用一个后台线程更快的代码生成的图像。 但是,你可以用它做的唯一的事情就是复制位图数据,所以你要么必须编写自己的基本绘图程序,或(甚至可能在你的情况下工作)创建一个“戳”的形象,你复制到任何一个地方的粒子走...



Answer 3:

在Windows窗体这几样让我退回到的东西;

  • 设置可见= False的最高级别的容器(如帆布形式本身)
  • 借鉴了很多
  • 设置可见=真

不知道是否WPF支持这一点。



Answer 4:

最快的WPF绘制方法,我发现是:

  1. 创建DrawingGroup“备份存储”。
  2. 的OnRender()时,画我的画组的绘图上下文
  3. 任何时候我想,backingStore.Open(),并吸引新的图形对象到它

这个对我来说,令人吃惊的事情,从Windows.Forms的未来..是我可以更新我的DrawingGroup我的OnRender过程中把它添加到的DrawingContext ()。 这在WPF更新现有留存绘图命令绘制树和触发的有效重绘。

在一个简单的程序,我在这两个Windows.Forms的和WPF(已经编码SoundLevelMonitor ),这种方法经验性感觉在性能上即时的OnPaint()GDI绘图非常相似。

我认为,WPF通过调用的方法OnRender(),它可能会更好地称为做了DIS-服务AccumulateDrawingObjects()

这基本上是这样的:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

我已经使用RenderTargetBitmap和WriteableBitmap的,不仅能够Image.Source也试过,并直接写入的DrawingContext。 上述方法是更快的。



Answer 5:

以下是一些您可以尝试的东西(我想他们与你的样品,它似乎快看(至少在我的系统)上)。

  • 使用画布,而不是网格(除非你有其他的原因)。 玩BitmapScalingMode和CachingHint:

     <Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False"> </Canvas> 
  • 添加静态资源在GeometryDrawing使用刷:

     <SolidColorBrush x:Key="MyBrush" Color="DarkBlue"/> 

在代码中使用如下:

    GeometryDrawing gd = new GeometryDrawing((SolidColorBrush)this.FindResource("MyBrush"), null, eg);

我希望这有帮助。



文章来源: Fast 2D graphics in WPF