C# WPF 中的快速 2D 图形

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16107877/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:02:59  来源:igfitidea点击:

Fast 2D graphics in WPF

c#wpfperformancedrawingdrawingvisual

提问by morishuz

I need to draw a large amount of 2D elements in WPF, such as lines and polygons. Their position also needs to be updated constantly.

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

I have looked at many of the answers here which mostly suggested using DrawingVisual or overriding the OnRender function. To test these methods I've implemented a simple particle system rendering 10000 ellipses and I find that the drawing performance is still really terrible using both of these approaches. On my PC I can't get much above 5-10 frames a second. which is totally unacceptable when you consider that I easily draw 1/2 million particles smoothly using other technologies.

我在这里查看了许多答案,这些答案主要建议使用 DrawingVisual 或覆盖 OnRender 函数。为了测试这些方法,我实现了一个简单的粒子系统渲染 10000 个椭圆,我发现使用这两种方法的绘制性能仍然非常糟糕。在我的 PC 上,我每秒不能超过 5-10 帧。当您考虑到我使用其他技术轻松平滑地绘制 1/2 百万个粒子时,这是完全不可接受的。

So my question is, am I running against a technical limitation here of WPF or am I missing something? Is there something else I can use? any suggestions welcome.

所以我的问题是,我是否遇到了 WPF 的技术限制,还是我遗漏了什么?还有什么我可以使用的吗?欢迎提出任何建议。

Here the code I tried

这是我试过的代码

content of MainWindow.xaml:

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>

content of MainWindow.xaml.cs:

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;
        }
    }
}

回答by FHnainia

Here are some of the things you may try: (I tried them with your sample and it seems to look faster (at least on my system)).

以下是您可以尝试的一些方法:(我用您的示例尝试了它们,它看起来更快(至少在我的系统上))。

  • Use Canvas instead of Grid (unless you have other reasons). Play BitmapScalingMode and CachingHint:

    <Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False">
    
    </Canvas>
    
  • Add a StaticResource for Brush used in GeometryDrawing:

    <SolidColorBrush x:Key="MyBrush" Color="DarkBlue"/>
    
  • 使用 Canvas 而不是 Grid(除非您有其他原因)。播放 BitmapScalingMode 和 CachingHint:

    <Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False">
    
    </Canvas>
    
  • 为 GeometryDrawing 中使用的 Brush 添加一个 StaticResource:

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

in code use as:

在代码中用作:

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

I hope this helps.

我希望这有帮助。

回答by Lawrence Kok

I believe the sample code provided is pretty much as good as it gets, and is showcasing the limits of the framework. In my measurements I profiled an average cost of 15-25ms is attributed to render-overhead. In essence we speak here about just the modification of the centre (dependency-) property, which is quite expensive. I presume it is expensive because it propagates the changes to mil-core directly.

我相信所提供的示例代码非常好,并且展示了框架的局限性。在我的测量中,我分析了 15-25 毫秒的平均成本归因于渲染开销。从本质上讲,我们在这里只讨论中心(依赖)属性的修改,这是非常昂贵的。我认为它很昂贵,因为它直接将更改传播到 mil-core。

One important note is that the overhead cost is proportional to the amount of objects whose position are changed in the simulation. Rendering a large quantity of objects on itself is not an issue when a majority of objects are temporal coherent i.e. don't change positions.

一个重要的注意事项是,开销成本与模拟中位置发生变化的对象数量成正比。当大多数对象是时间连贯的,即不改变位置时,在自身上渲染大量对象不是问题。

The best alternative approach for this situation is to resort to D3DImage, which is an element for the Windows Presentation Foundation to present information rendered with DirectX. Generally spoken that approach should be effective, performance wise.

这种情况的最佳替代方法是求助于D3DImage,它是 Windows Presentation Foundation 的一个元素,用于呈现使用 DirectX 呈现的信息。一般来说,这种方法应该是有效的,性能明智的。

回答by hbarck

You could try a WriteableBitmap, and produce the image using faster code on a background thread. However, the only thing you can do with it is copy bitmap data, so you either have to code your own primitive drawing routines, or (which might even work in your case) create a "stamp" image which you copy to everywhere your particles go...

您可以尝试使用 WriteableBitmap,并在后台线程上使用更快的代码生成图像。但是,您唯一能做的就是复制位图数据,因此您要么必须编写自己的原始绘图例程,要么(甚至可能适用于您的情况)创建一个“图章”图像,然后将其复制到粒子的任何位置走...

回答by IvoTops

In windows forms these kind of things made me fall back to;

在 windows 窗体中,这些东西让我回过头来;

  • Set Visible=False for the highest level container (e.g. canvas of the form itself)
  • Draw a lot
  • Set Visible=True
  • 为最高级别的容器(例如表单本身的画布)设置 Visible=False
  • 画了很多
  • 设置可见=真

Not sure if WPF supports this.

不确定 WPF 是否支持这一点。

回答by David Jeske

The fastest WPF drawing method I have found is to:

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

  1. create a DrawingGroup "backingStore".
  2. during OnRender(), draw my drawing group to the drawing context
  3. anytime I want, backingStore.Open() and draw new graphics objects into it
  1. 创建一个绘图组“backingStore”。
  2. 在 OnRender() 期间,将我的绘图组绘制到绘图上下文
  3. 随时,backingStore.Open() 并在其中绘制新的图形对象

The surprising thing about this for me, coming from Windows.Forms.. is that I can update my DrawingGroup afterI've added it to the DrawingContext during OnRender(). This is updating the existing retained drawing commands in the WPF drawing tree and triggering an efficient repaint.

对我来说,来自 Windows.Forms 的令人惊讶的事情是,在 OnRender() 期间将它添加到 DrawingContext之后,我可以更新我的 DrawingGroup 。这将更新 WPF 绘图树中现有的保留绘图命令并触发有效的重绘。

In a simple app I've coded in both Windows.Forms and WPF (SoundLevelMonitor), this method empirically feels pretty similar in performance to immediate OnPaint() GDI drawing.

在我在 Windows.Forms 和 WPF ( SoundLevelMonitor) 中编写的一个简单应用程序中,根据经验,此方法在性能上与即时 OnPaint() GDI 绘图非常相似。

I think WPF did a dis-service by calling the method OnRender(), it might be better termed AccumulateDrawingObjects()

我认为 WPF 通过调用方法 OnRender() 做了一个破坏服务,它可能更好地称为 AccumulateDrawingObjects()

This basically looks like:

这基本上看起来像:

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();            
}


I've also tried using RenderTargetBitmap and WriteableBitmap, both to an Image.Source, and written directly to a DrawingContext. The above method is faster.

我还尝试使用 RenderTargetBitmap 和 WriteableBitmap,两者都用于 Image.Source,并直接写入 DrawingContext。上面的方法比较快。