How to: High performance graphics in WPF

2 במרץ 2008

12 תגובות

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/how-to-high-performance-graphics-in-wpf/]



Microsoft DPE: "WPF is ever best super performance oriented technology for creating hyper multi point graphs, using parallelism and huge in-memory persistence vector scrounged math distributed calculations… And ever more with branded new Microsoft Windows Vista 7.


Client: Errr, well…. Let's try to apply it for our VB program…


DPE: You can easily do it yourself, but it'd be better to call someone from Microsoft Consulting Services.


Client: Let it be…


MCS: Well. It's too huge for WPF to scale… WPF uses a retained rendering system. It saves every little pixel and make you able scale and repaint very often without the composition system blocking on callbacks to your code. However, 1,000,000 retained pixels is too huge to scale…


Client: I want it scale. They promised… They told, it'll scale. Make it to do what I want it to do!!!


MCS: Errr, well. Let it be!


This is very common dialog between DPE, MCS and clients. Sales men want it to do, what he need it to do. Client want it to do what sales men promised to do and Services men should make it to do what they both want it to do. Today we'll speak about retained, lazy and loose models to produce large scale graphics.


First problem: multithreading


Even before we start to work, you should know, that we cannot create dependency objects in thread other, then current UI thread. We can use locks, mutexes, semaphores, however we still can not create Dependency Objects in other thread. In order to get rid of it, we'll have to use INofityPropertyChanged implementation, instead of Dependency Objects. This means, no Dependency Properties.


So, we'll start with following code (I'll reuse nice code written by Luis Diego Fallas to create Mandelbrot Fractal set)


class FractsCollection : INotifyPropertyChanged
    {


Second problem: rendering thread


Well, the problem is knows. There is only one UI thread. We wont it be only one, so we'll use our own HostVisual by Dwayne Need to enhance the performance.


Third problem: Retained objects


Actually, this is not problem. This is feature. And it can be extremely useful if you want to retain layout. Let's create a simple example: Dragon curve fractal. It has limited number of points, generated by well known final algorithm. So, we'll create our own geometry, derived from Shape class. The fasted geometry is StreamGeometry, so let's use it. First of all let's create the class and save the array of points.



public class DragonShape:Shape
    {
StreamGeometry dragonGeometry;
        double _angle;
        List<Point> Points;


Then we'll generate the pattern



void GeneratePattern()
        {
            ThreadPool.QueueUserWorkItem(delegate
            {
Move(5);
Turn(GetNextPoint * System.Math.PI / 180.0);


Then, by overriding DefiningGeometry property, create the fractal



protected override System.Windows.Media.Geometry DefiningGeometry
        {
            get { 
                using (StreamGeometryContext context = dragonGeometry.Open())
                {
                    context.BeginFigure(Points[0], false, false);
                    context.PolyLineTo(Points, true, false);
                }
                return (Geometry)dragonGeometry.GetAsFrozen();}
        }


Don't forget to tell the shape, that geometry was changed



this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (SendOrPostCallback)delegate
                    {
                        this.InvalidateVisual();
                    }, null);


Now, run it and we'll have very nice vector fractal generated that can be easily resized and scaled. Here the result.


image image


This method will work fine for 1,000, even 10,000 points. But after a while you'll experience performance degradation. What to do? The client wants 10,000,000 (!) points (and in Winforms GDI+ it works for him)


Let's try to understand why. Because it is not retain. It's image! So, let's use image to make the play fair.


The fastest BitmapSource is InteropBitmap. It has an ability to update itself from the memory section. That's exactly what we'll use



format = PixelFormats.Bgr32;
            max = format.BitsPerPixel;
            uint count = (uint)(sWidth * sHeight * format.BitsPerPixel / 8);
            section = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04, 0, count, null);
            map = MapViewOfFile(section, 0xF001F, 0, 0, count);
            pixels = new byte[count];
            Marshal.Copy(pixels, 0, map, (int)count);
            source = System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection(section, (int)sWidth, (int)sHeight, format, (int)(sWidth * format.BitsPerPixel / 8), 0) as InteropBitmap;
            ThreadPool.QueueUserWorkItem(delegate
            {
                while (true)
                {
                    Generate();
                }
            });


To get the source to bind to we'll get frozen image. Call Invalidate first to reread the source.



InteropBitmap source;
        public BitmapSource Source
        {
            get
            {
                source.Invalidate();
                return (BitmapSource)source.GetAsFrozen();
            }
        }


Now, when we ready to display we can just put pixels simultaneously (by using Parallel extension and PLINQ) and tell the WPF that our count and ImageSource property updated upon each pixel.



unsafe
            {
                uint* pBuffer = (uint*)map;
                Parallel.For(0, (int)sHeight, delegate(int yi)
                {
                    foreach (var p in from xi in Enumerable.Range(0, (int)sWidth).AsParallel()
                                      let mappedX = xF(xi)
                                      let mappedY = yF(yi)
                                      let p0 = new TranslatePoint(xF(xi), yF(yi))
                                      let function = constructor(p0)
                                      select new
                                      {
                                          x = xi,
                                          y = yi,
                                          xD = mappedX,
                                          yD = mappedY,
                                          i = apply(function, p0)
                                                  .TakeWhile(
                                                     (x, j) => j < max && x.NormSquared() < 4.0)
                                                  .Count()
                                      })
                    {
                        pBuffer[(int)(p.x + p.y * sWidth)] = (uint)(uint)((uint)0xFF << 24) |
                             (uint)(p.i << 16) |
                             (uint)(5*p.i << 8) |
                             (uint)(15*p.i); ;
                        count++;
                        FireUpdate();
                    }
                });
            }


void FireUpdate()
        {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("PixelCount"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Source"));
                }
        }


We done. Now let's see how fast it can be to generate and display 10,000,000 live pixels (it's about 16,000×16,00x32bit image) in screen. The maximum, I was able to get with my Dell Latitude D820 was 1,200,000 x 1,200,000 pixels indexed 4 bit image (it's about 100,000,000 points) and my memory is over 🙂



Not bad, ah? So WPF scales and DPE are right? Not quite right, but let them to do their work and we'll be behind to come all client's dreams (and DPE's promises) true.


Have a nice day and be good people. Now you can use WPF for drawing big number of points.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

12 תגובות

  1. Eric Hill4 במרץ 2008 ב 4:12

    Tamir,

    I'm one of the people regularly complaining about the need to solve the million-point problem in WPF, so I am thrilled to have another approach to try, so thank you. I'm curious, though, if you know what relationship there might be between your approach with InteropBitmap and the new and improved WriteableBitmap class that is coming in beta in late Spring in the next update to WPF?

    Thanks,

    Eric

    הגב
  2. Tamir Khason4 במרץ 2008 ב 9:28

    Eric, WritableBitmap is good candidate for such work, however, it still much slower, then InteropBitmap, that, actually, refreshes itself directly from memory section.

    הגב
  3. Peter Bromberg9 במרץ 2008 ב 2:51

    This is really hot struff. Reminds me of Benoit Mandelbrot's coffe-table book on fractals. I hope you'll write some more and include downloadable sample Visual Studio soln.

    הגב
  4. Adiel28 באפריל 2008 ב 17:48

    Hi
    This is very intresting article.
    Could you please upload the source?
    Thanks

    הגב
  5. Nirupama7 במאי 2008 ב 8:52

    Hi,
    I wanted to develop a WPF project that has very intense performance requirements, and I thought it was best to consult you on this before designing the app.

    The application would be rendering data obtained from a medical hardware as charts. For this, the requirements are:

    1) Requires rendering of almost 250 points every 4 milliseconds in a chart (to be connected by a line or Bezier curve). The rendered points will also have a spectrum of colors (as in a heatmap).
    2) The rendered points would move when new data is pumped by the hardware and rendered (as in a ECG)
    3) At any given point there could be over a 150,000 moving points on the chart (because the chart window would display data of 3-5 seconds).
    4) Also later we would like to retain the history of data points of past 60 seconds or so.
    5) There could be upto 12 such chart displays running parallel at the same time in a window on the application.

    I seek your thoughts on what could be the best approach for rendering these points and animating them. Some kind of selectively invalidating the UI would be desirable here. Also what are the recommended options for storing this much data for history purpose. The problem demands something at a very low level in the WPF stack.

    How much workable would it be to draw a very specific curve using interop bitmap method. Given the requirement what will suit best?

    Any Help would be highly appreciated,
    Thanks in Advance

    הגב
  6. Tamir Khason7 במאי 2008 ב 8:57

    Nirupama, if there is user interaction (mouse over, etc), you probably should check WritableBitmap. If not, use InteropBitmap, but it tiers

    הגב
  7. Nirupama19 במאי 2008 ב 17:40

    Hi,

    Thanks a lot for the response..Writeable Bitmap fits really well in the scenario. Am able to create a smooth running graph at high rate using the writeable bitmap..

    Another challenge on that is whether would I be able to Scale the graph using some smart way rather than performing basic bitmap scaling.

    Also when I am asigning the bitmap source from writeable bitmap as a source to Image, the changes in the dPI value does not reflect,, are the wPF elements like Image or canvas the culprits… I need to change the DPI settings of the image from the default of 96..

    Thanks in Advance
    Nirupama

    הגב
  8. Nirupama26 במאי 2008 ב 17:16

    Hi..

    Thsi is a gentle reminder..for the above query…
    Please do give in your inputs…That would be of great help..

    Thanks and Regards,
    Nirupama

    הגב
  9. Tamir26 במאי 2008 ב 17:52

    Nirupama, I think, that the best choice for you is InteropBitmap. It allows you to create and fully manage underlying image source. Seek for it in my blog

    הגב
  10. Sean McDirmid15 בספטמבר 2008 ב 8:27

    I tried to reproduce your code but it seems fairly impossible: too many things are missing. Is this meant to be a sample or are there lots of proprietary things so you can't provide all the code?

    הגב
  11. Roman21 באוקטובר 2008 ב 17:21

    Hi
    I have the same problem as Nirupama.
    I need to display 4000 objects and each object contain 100 polyline segments.
    Could you please upload the source?
    Any Help would be highly appreciated.

    Thanks
    Roman

    הגב
  12. Evgen26 בדצמבר 2008 ב 1:54

    Hi

    This is very intresting article.

    Could you please upload the source?

    Thanks

    הגב