5

Background

I'm working on an application that is supposed to work on every windows platform from XP onwards in the same manner. Through the .NET framework this has been very easy, for the most part. The application runs across a variety of touch surfaces. The application targets .Net 3.0, but if for some reason I should move to .Net 3.5, I can do that. I am unable to use 4.0.

The application makes heavy use of GDI+ through the System.Drawing namespace. Now, I understand that GDI+ either isn't hardware accelerated at all or is only accelerated in a very small number of graphics cards, so I expect to have some performance issues. However, this issue is blaring and makes the application's usability significantly lower. I would rather not rewrite the entire application to target DirectX or OpenGL if it can be avoided with an easy fix.

The Problem

We recently installed Windows 7 64-bit on one of the touch tables. It was the first time the application was run on a Windows 7 64-bit machine. In any event, any drawing (specifically DrawLine) with more than one finger on the device at a time causes a ridiculous amount of lag. This lag isn't apparent on 32-bit Windows XP or 32-bit Windows 7, so I think it might be specific to 64-bit Windows 7 (I don't have a 64-bit XP machine to test with).

The application was also forced to run as a 32-bit application due to one of the .dll files only have 32-bit libraries available to compile it. I read somewhere that forcing a process into 32-bit mode on a 64-bit system could cause performance issues, but when I upgraded the SDK we were using and made a 64-bit specific .dll and application, the problem persisted.

I read on this other StackOverflow thread that there should be no differences between a 32-bit and a 64-bit application with relation to GDI+. This doesn't seem to be the case here.

So what it comes down to: Do any of you know why there might be such a huge performance difference between Windows 32-bit and Windows 64-bit? If I can't find a solution, I'll have to spend a bit of time making everything use hardware accelerated drawing. I would like to avoid that if at all possible.

Thanks everyone!

EDIT: Requested code snippet

    public delegate void drawLineDelegate(Color color, float width, int x1, int y1, int x2, int y2);
    public void drawLine(Color color, float width, int x1, int y1, int x2, int y2)
    {
        if (InvokeRequired)
        {
            Invoke(new drawLineDelegate(drawLine), new object[] { color, width, x1, y1, x2, y2 });
        }
        else
        {
            try
            {
                lock (drawLock)
                {
                    pen.Width = width;
                    pen.Color = color;
                    scaledPen.Width = width * this.Width / Canvas.Width;
                    scaledPen.Color = color;
                    Point p1 = scalePointToScreen(new Point(x1, y1));
                    Point p2 = scalePointToScreen(new Point(x2, y2));

                    graphics.DrawLine(pen, x1, y1, x2, y2);
                    scaledGraphics.DrawLine(scaledPen, p1.X, p1.Y, p2.X, p2.Y);

                    this.Invalidate(new Rectangle(Math.Min(p1.X, p2.X) - (int)scaledPen.Width, Math.Min(p1.Y, p2.Y) - (int)scaledPen.Width,
                          Math.Abs(p2.X - p1.X) + 2 * (int)scaledPen.Width, Math.Abs(p2.Y - p1.Y) + 2 * (int)scaledPen.Width));
                }
            }
            catch (Exception ex)
            {
                if (Scribbler.Properties.Settings.Default.LogLevel >= 2)
                {
                    Scribbler.ScribblerForm.WriteLogMessage(ex.ToString());
                }
            }
        }

Edit: Further Research I did some more scouring of the internet with relation to the NVidia cards we use on our machines. This lead me to several posts on the NVidia Developer Forums about people with the same issue. The common answer is that GDI is deprecated on Windows 7 and the newer NVidia graphics drivers (from 182.5 onwards) suffer from GDI performance issues. The solutions were either to update to Direct3D or use old drivers. Does anyone know for certain whether this is the best course of action?

Community
  • 1
  • 1
red_sky
  • 834
  • 9
  • 17
  • WPF would be accelerated, giving you the performance of DirectX without actually having to make OpenGL or DirectX calls. – Ben Voigt Jul 21 '11 at 20:56
  • 2
    Are you sure the issue is GDI? You should measure timings. Were both Win 7 systems running with Aero turned on? –  Jul 21 '11 at 21:36
  • Both the 32-bit Windows 7 and the 64-bit Windows 7 were running with Aero turned on. The reason I think it's GDI+ related is that only the drawing itself is slowed. The GUI still runs smoothly. We had another issue like this on the same table on all versions of the application, but forcing the application to use only one core resolved it. This was because GDI+ has internal blocking so it can only be accessed by one thread at a time. One core alleviated this. I think maybe the new issue is related? And Thanks for the WPF tip, I will have to look into ways of drawing the same as gdi+. – red_sky Jul 21 '11 at 21:45
  • What does the "more than one finger" aspect have to do anything? Do lines draw faster if you only use one finger? – Gabe Jul 25 '11 at 19:51
  • GDI is not deprecated, nor will it ever be. GDI runs so many Windows applications that this would be suicide for Microsoft, only a tiny percentage of commercial apps will use anything but GDI. – Erik Funkenbusch Jul 25 '11 at 19:56
  • @Gabe, the drawing code runs perfectly fast until you have more than one finger down (which draws as many lines as there are fingers on the device). At that point, it gets very slow. – red_sky Jul 25 '11 at 20:23
  • @Mystere Man, that's what I figured but it seems to be a popular answer on the Nvidia Forums. – red_sky Jul 25 '11 at 20:23
  • @red_sky: So you're saying that what's slow is drawing multiple lines simultaneously? – Gabe Jul 25 '11 at 20:27
  • Yes, that's the slow part. I assume it has something to do with the fact that when movement is detected on a blob (a finger), a line segment is drawn between the blob's current position and its last position. The threshold is something like 10 pixels. When there are, for example, five fingers on the screen and moving at the same time, the amount of line drawing operations is rather large. – red_sky Jul 25 '11 at 20:38
  • @red_sky: Maperitive renders maps with tens of thousands of line segments in real-time with a linear drop of performance. Your fingers definitively do not present a problem for GDI+ - the problem is in the overall architecture and how GDI+ is used. – Igor Brejc Jul 25 '11 at 20:53
  • @Igor Brejc: There's a strong possibility that it's the same issue we had before with threading and GDI drawing, though that problem was fixed (or appears to be on other system configuration). I've been looking through the source trying to figure out why the code is executed on multiple threads and the only thing I have found is that remoting uses multiple threads under the hood. One thing that I have thought of would be to do all of the drawing in OnPaint and where we have existing draw code, invalidate the control instead. Is this a bad idea or will it not fix anything? – red_sky Jul 25 '11 at 21:01
  • @red_sky: The one core issue is a huge red flag. Simply put, this app seems like it's been patched and repatched so many times that nobody is sure anymore of what or how it's doing anything. Simply put, the drawing system needs to be re-engineered. You will likely flail about trying to patch this and trying to make it work. Maybe you'll get lucky and find some patch that seems to work.. that is until the next OS change, service pack, or hardware upgrade. Also, if you're an intern, this level of change is probably above your skill level. – Erik Funkenbusch Jul 25 '11 at 21:35
  • @red_sky: To elaborate more on the one core issue, it means you have multi-threading deadlocks somewhere, where one piece of code acquires some resource another thread needs, then the first needs something the other thread owns, so they fight it out between each other. Multi-core issues are usually a very serious symptom of poor design. – Erik Funkenbusch Jul 25 '11 at 21:37
  • @Mystere Man I do believe that GDI+ itself has internal locking that could very well be causing the slowdowns that are visible (due to poor design on our part). I'll have a talk with my supervisor about the issue and see what she has to say about it. Thanks for the feedback. – red_sky Jul 26 '11 at 01:03

2 Answers2

1

First of all I'm not sure you'll get a lot of performance benefits in using WPF. If you do decide to rewrite your graphics code, I suggest moving to Direct2D (you can still write the code in C#). Here's an article comparing the performance of all of the three libraries: http://www.teechart.net/reference/articles/WhitePaper_Direct2D.htm

I'm a developer of Maperitive, which heavily uses GDI+ to render maps. I cannot say that I noticed any major performance issues on 64bit Windows 7, but then again I'm not really testing it on any other system (apart from Ubuntu's Mono on a netbook, which is understandably a lot slower).

I guess it would be easier to discuss this if you showed us some snippets of your line drawing code. On the other hand the problem could be hardware related (graphics card driver?). How many different Win 7 64bit configurations have you tested this on?

UPDATE:

About "deprecated" GDI on Windows 7: I very much doubt that would be true because you would then notice performance problems not only on your application and I doubt Microsoft would be bold enough to do something like this for backward compatibility reasons. Do you have any links that prove this claim?

About your code: you haven't shown all of it and its real purpose, but from the context I suspect you use the Graphics object created using Control.CreateGraphics() method (and you call the line drawing from other threads too, which can be a performance issue in itself). I suspect you do this to draw lines interactively (instead of redrawing everything through OnPaint() method). In general this practice is problematic (here's Bob Powell's explanation).

BTW what is the difference between graphics and scaledGraphics?

If you could explain what exactly you are trying to achieve through this method, maybe we can find a better (faster) way to do this.

Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
Igor Brejc
  • 18,714
  • 13
  • 76
  • 95
  • I'm sorry it took me so long to reply to your post. I was without internet for the weekend. The problem with moving to Direct2D is that over half of our clients use XP still. We're still discussing whether we would use OpenGL or DirectX if we need to. At this point in time, we've only tested the application on one Win7 64-bit configuration. The problem is, it's the most common configuration of our clients, so it's important that it works on that table. As far as the drawing code is concerned, we have a custom control that sits on the main form. We have an instance of it and call its draw – red_sky Jul 25 '11 at 13:19
  • functions which just make calls to graphics.drawline I'll post some of the code in the original post, however. – red_sky Jul 25 '11 at 13:22
  • 1
    Thanks for the link to Bob Powell; the index is still up, and if you replace the ".htm" with ".aspx" on the links they appear to work as well. – Mark Ransom Jul 25 '11 at 20:10
  • I've been on Bob Powell's site a few times, but I don't remember what for exactly. The scaledGraphics is used to scale down whatever gets sent through the network to the client's resolution instead of leaving it the sender's resolution. graphics is also used to create actual-size images of what has been drawing (as well as holding the original resolution drawings). The graphics objects themselves are created from an image when the background is updated (which doesn't happen often). Line drawing isn't intentionally called from other threads, but it ends up happening as a result of the – red_sky Jul 25 '11 at 20:26
  • as a result of the server / client asynchronous setup. We initially had problems with performance due to the threading issue until we forced the application to use only one core. That seemed to fix it on everything except a Windows 7 64-bit configuration. – red_sky Jul 25 '11 at 20:28
  • Client? Server? Now you really got me confused :). I think you have a lot of issues which are not really directly related to GDI+ and which can seriously affect the performance. Also: creating `Graphics` from an image - image of what? Screen? Another problematic thing from the performance standpoint. – Igor Brejc Jul 25 '11 at 20:41
  • Oh, and as for proof I don't have any. I wasn't trying to make the claim that it was deprecated, only that I had read other's posts suggesting that it was. I apologize if it seemed like I was trying to say that. http://forums.nvidia.com/index.php?showtopic=157780 among other posts on the nvidia developer forums suggest that it is deprecated. – red_sky Jul 25 '11 at 20:43
  • The graphics are created from a Bitmap which is created from a MemoryStream when the setBackground function is called. These MemoryStreams are created from from a few p/invokes that create a bitmap from the desktop handle and the resolution. – red_sky Jul 25 '11 at 20:48
  • @red_sky: how often is this bitmap created? – Igor Brejc Jul 25 '11 at 20:54
  • Very infrequently. It's created once when the application is started and when the user manually updates the image to get a new desktop background image. There is no auto-updating going on. – red_sky Jul 25 '11 at 20:57
1

I notice that you're locking your drawing code. Are you drawing from multiple threads? I suspect that the lock may have something to do with your performance issues.

You shouldn't draw from multiple threads unless you know EXACTLY what you are doing. There are way too many gotchas in the OS regarding multithreaded graphics use.

Because you have an Invoke in there, that is a very slow process. You don't want to be invoking all the time. You should never be calling this method from a background thread. It also seems like your lock is not really needed, because your invoke would prevent any thread access other than the message pump thread.

I think you would benefit greatly from redesigning your painting workflow to ensure it's called once, and only from the main message pump thread.

I'm also a bit confused about why you're invalidating areas within your drawing code.. that's likely to just create an infinite draw loop, which could also be part of your problem.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • I actually can't explain why there's both a lock and an invoke, or why the areas are being invalidated. That was written by another individual before I came to work on the project. As for the threading issue, there's nowhere in the code that we wrote that creates background threads. I suspect the reason there are multiple threads is due to our asynchronous server / client setup (using .NET Remoting). – red_sky Jul 25 '11 at 20:30
  • @Mystere Man, I 100% agree with your comments. This code seems break a lot of rules as regards to GDI+ usage and then GDI+ gets to be blamed. – Igor Brejc Jul 25 '11 at 20:45
  • @red_sky: the client should not directly cause anything to be drawn by GDI+. This is not how GDI+ is supposed to be used and if you try to force it, it moans and groans. Instead, the client requests should be sent to a queue and processed by the server in batches (or though the OnPaint method). Also, I'm not really sure why the server has to do a real-time rendering for clients, too. What kind of clients are we talking about? – Igor Brejc Jul 25 '11 at 20:48
  • The rendering isn't real time on the client side. Actions are sent to the server in chunks, which are then sent to the clients in chunks. The actions are arrays containing the command to execute and the coordinates associated with the action. The client then goes through the array of commands and performs them. I can assure you that this isn't where the performance issue is. It's only when a person is drawing with multiple fingers on one of the touch tables, as I have mentioned in the original post. – red_sky Jul 25 '11 at 20:53
  • If this is true, then you can do a simple experiment: remove all the client-server code, all the threading code, all the locking code etc and test the pure rendering with one finger and several fingers. And run your code through a profiler. Without profiling any assurances aren't worth too much and can only mislead you. – Igor Brejc Jul 25 '11 at 21:01
  • That sounds like a good idea, at the very least it will help narrow down what exactly is causing the problem. Thanks for your guidance thus far. I'll be honest, this is my first internship and it's a project that was pretty far along when I started working on it. I'm not used to professional practices yet (using profilers) and I would really rather write all of my own code than work off of others'. – red_sky Jul 25 '11 at 21:05