2D Drawing Performance (GDI+ vs SlimDX)

13,976

Solution 1

I use GDI for my cartographic application. While GDI+ is slower than, say, DirectX, I find that there are a lot of things and tricks that can be used to speed things up. A lot of CPU is used for preparing the data before drawing it itself, so GDI should not be the only bottleneck there.

Things to look out for (and these are general enough to apply to other graphics engines, too):

  1. First of all: measure. Use a profiler to see what is the real bottleneck in your code.
  2. Reuse GDI primitives. This is vital. If you have to draw 100,000 graphics objects that look the same or similar, use the same Pen, Brush etc. Creating these primitives is expensive.
  3. Cache the rendering data - for example: don't recalculate gfx element positions if you don't need to.
  4. When panning/zooming, draw the scene with lower GDI+ quality (and without expensive GDI operations). There are a number of Graphics object settings to lower the quality. After the user stops panning, draw the scene with the high quality.
  5. A lot and lot of little things that improve performance. I've been developing this app for 2-3 years (or is it 4 already hmm?) now and I still find ways to improve things :). This is why profiling is important - the code changes and this can affect the performance, so you need to profile the new code.

One other thing: I haven't used SlimDX, but I did try Direct2D (I'm referring to Microsoft.WindowsAPICodePack.DirectX.Direct2D1). The performance was considerably faster than GDI+ in my case, but I had some issues with rendering bitmaps and never had the time to find the proper solution.

Solution 2

I have recently ported some drawing code over to DirectX and have been very pleased with the results. We were mainly rendering bitmaps using bit-bashing and are seeing render times that could be measured in minutes reduced to around 1-2 seconds.

This can't be directly compared to you usage, as we've gone from bit-bashing in C++ to Direct3D in C# using SlimDX, but I imagine you will see performance benefits, even if they're not the orders of magnitude we're seeing.

I would advise you to take a look at using Direct2D with SlimDX. You will need to use DirectX 10.1 as Direct2D isn't compatible with DirectX 11 for some reason. If you have used the drawing API in WPF then you will already be familiar with Direct2D as its API is based on the WPF drawing API as far as I can tell. The main problems with Direct2D are a lack of documentation and the fact it only works in Vista onwards.

I've not experimented with DirectX 10/WPF interop, but I believe it is possible (http://stackoverflow.com/questions/1252780/d3dimage-using-dx10)

EDIT: I thought I'd give you a comparison from our code of drawing a simple polygon. First the WPF version:

        StreamGeometry geometry = new StreamGeometry();

        using (StreamGeometryContext ctx = geometry.Open())
        {
            foreach (Polygon polygon in mask.Polygons)
            {
                bool first = true;

                foreach (Vector2 p in polygon.Points)
                {
                    Point point = new Point(p.X, p.Y);

                    if (first)
                    {
                        ctx.BeginFigure(point, true, true);
                        first = false;
                    }
                    else
                    {
                        ctx.LineTo(point, false, false);
                    }
                }
            }
        }

Now the Direct2D version:

        Texture2D maskTexture = helper.CreateRenderTexture(width, height);

        RenderTargetProperties props = new RenderTargetProperties
        {
            HorizontalDpi = 96,
            PixelFormat = new PixelFormat(SlimDX.DXGI.Format.Unknown, AlphaMode.Premultiplied),
            Type = RenderTargetType.Default,
            Usage = RenderTargetUsage.None,
            VerticalDpi = 96,
        };

        using (SlimDX.Direct2D.Factory factory = new SlimDX.Direct2D.Factory())
        using (SlimDX.DXGI.Surface surface = maskTexture.AsSurface())
        using (RenderTarget target = RenderTarget.FromDXGI(factory, surface, props))
        using (SlimDX.Direct2D.Brush brush = new SolidColorBrush(target, new SlimDX.Color4(System.Drawing.Color.Red)))
        using (PathGeometry geometry = new PathGeometry(factory))
        using (SimplifiedGeometrySink sink = geometry.Open())
        {
            foreach (Polygon polygon in mask.Polygons)
            {
                PointF[] points = new PointF[polygon.Points.Count()];
                int i = 0;

                foreach (Vector2 p in polygon.Points)
                {
                    points[i++] = new PointF(p.X * width, p.Y * height);
                }

                sink.BeginFigure(points[0], FigureBegin.Filled);
                sink.AddLines(points);
                sink.EndFigure(FigureEnd.Closed);
            }

            sink.Close();
            target.BeginDraw();
            target.FillGeometry(geometry, brush);
            target.EndDraw();
        }

As you can see, the Direct2D version is slightly more work (and relies on a few helper functions I've written) but it's actually pretty similar.

Solution 3

Let me try and list the pros and cons of each approach - which will perhaps give you some idea about which to use.

GDI Pros

  • Easy to draw vector shapes with
  • No need to include extra libraries

GDI Cons

  • Slower than DX
  • Need to limit "fancy" drawing (gradients and the like) or it might slow things down
  • If the diagram needs to be interactive - might not be a great option

SlimDX Pros

  • Can do some fancy drawing while being faster than GDI
  • If the drawing is interactive - this approach will be MUCH better
  • Since you draw the primitives you can control quality at each zoom level

SlimDX Cons

  • Not very easy to draw simple shapes with - you'll need to write your own abstractions or use a library that helps you draw shapes
  • Not as simple to use a GDI especially if you've not used it before

And perhaps more I forgot to put in here, but perhaps these will do for starters?

-A.

Share:
13,976
Admin
Author by

Admin

Updated on June 17, 2022

Comments

  • Admin
    Admin almost 2 years

    I am part of a team that has created a tool to view and interact with very large and heavily interconnected graphs in C#/WPF. Viewing and interacting with the graph is done through a custom control that takes in a set of DrawingVisuals and displays them on a canvas. Nodes in the graph may have a custom shape created with our editor. The current control works very well and is fairly coupled with our program but there are legitimate worries about performance when considering much larger graphs (20,000+ nodes and lots of connection).

    After doing a bit of research it seems the two approaches are:

    • A GDI+ route where graphics are drawn to a WriteableBitmap or InteropBitmap.
    • SlimDX or DirectX variant (hosted in a D3DImage)


    Given these two extremely different approaches which route would be best to take considering:

    • Interacting with the graph must be fast even while viewing the whole graph.
    • Updating the visuals should be fast (color or size change)
    • Hit testing must be fast (point and rectangle).
    • Development must be completed in a timely manner.

    Which method would you use and why?

    EDIT:
    It looks like a similar question was asked but not answered.

  • Admin
    Admin about 12 years
    Thank you for your response! Do you have any experience using SlimDX (wpf integration)? Is there an upper limit to the amount of things it can render? I'm trying to weigh the learning curve of SlimDX with the performance advantages it offers vs the GDI route.
  • Ani
    Ani about 12 years
    I believe SlimDX has a WPF interoperability sample (code.google.com/p/slimdx/source/browse/trunk/samples/Direct‌​3D10/…). Also, while there is no upper limit per se to what SlimDX/DirectX can render, remember that performance is only a function of graphics card horsepower and your rendering efficiency - actually this is true of both GDI and DX. I would recommend going down the GDI route first and take the SlimDX recourse if the performance does not match your needs.
  • Admin
    Admin about 12 years
    I'm curious as to why you recommend the "GDI" way of doing things over SlimDX. Is the learning curve of SlimDX that high? There is a worry that implementing the GDI way (rendering, fast spatial selection, and integration) just to find that it is too slow would be a "waste" of time when going with the GPU accelerated route in the first place would solve the issue. Again, thanks for responding.
  • Ani
    Ani about 12 years
    I recommend the GDI way to begin with because with SlimDX, you'll have to write a lot more low-level drawing and management code than if you do it in GDI and personally I wouldn't go down that road unless it's justified. I would recommend an experiment with numbers and loads you might encounter in the final app, just to make sure.
  • Admin
    Admin about 12 years
    The project leader is wary of going the GDI+/Bitmap route because of his past experience of it being slow (he may be right or wrong). He desires a performance based solution and its stuck in his head that DirectX is the solution. Do you think that GDI+ will be fast enough for 30fps and interactive with 100k objects?
  • Igor Brejc
    Igor Brejc about 12 years
    @CBent: Hard to say, it very much depends on the nature of what you're drawing (and hardware, obviously). But here's something I just stumbled upon myself and will definitively experiment for my own application: code.google.com/p/sharpdx. Claims to be the fastest managed DirectX library - if it's faster than WindowsAPICodePack, then it's truly real fast!
  • Igor Brejc
    Igor Brejc about 12 years
    @CBent: and oh yes, DirectX is usually the way to go when you require fast graphics, so I guess your project leader's thinking is right. I went with the GDI+ route because it was Mono-portable - I have users running my app on Linuxes and Macs.
  • Admin
    Admin about 12 years
    I appreciate your input. I have actually already started experimenting with SharpDX. Unfortunately, the learning curve is quite high and there are no developers at my company that have this kind of experience so I am learning on my own. Our application is targeted at Windows only so we don't have to worry about portability.
  • Admin
    Admin about 12 years
    I'm hoping that by going to a DirectX variant (currently looking at SharpDX) our application's rendering will improve as much as yours. We are drawing simple lines and 2D polygons (but so many of them that WPF starts choking). What resources did you use for SlimDX research?
  • Grokys
    Grokys about 12 years
    I'm afraid this was done a year or so ago, so I can't remember the exact resources I used. SlimDX sticks pretty closely to the Direct2D COM API though, so any examples in C++ can be converted over to C#/SlimDX pretty easily, which is what I believe I did.
  • Admin
    Admin about 12 years
    Wow! I just got your edited post with the included code. They are very similar indeed. Did you have to change any of your polygon formats to make them compatible for SlimDX? Is your application a full screen application, a single DX surface, or do you have many surfaces shown at once?
  • Grokys
    Grokys about 12 years
    My application is actually an offline renderer which renders to a .png file, so nothing is actually displayed on-screen. I use a polygon format consisting of SlimDX.Vector2 structs, which use floats, though I guess you could just use the WPF polygon objects and convert the doubles there into floats on the fly if you don't mind the overhead.
  • Grokys
    Grokys about 12 years
    (I should add that I imagine the overhead of converting double to float is very small)
  • Igor Brejc
    Igor Brejc about 12 years
    @CBent BTW SharpDX uses very similar class hierarchy.
  • Igor Brejc
    Igor Brejc about 12 years
    About the history of SlimDX and SharpDX, interesting read: code4k.blogspot.com/2010/11/…
  • Ivan Peric
    Ivan Peric about 10 years
    @Groky Could you, please, post the code behind this line of code: Texture2D maskTexture = helper.CreateRenderTexture(width, height); I am trying to do the same thing as you but my RenderTexture creation method has some errors, which I can't solve: stackoverflow.com/questions/22934324/…
  • GorillaApe
    GorillaApe over 9 years
    Late. What kind of numbers do you use double or float?